[{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"Spring boot provides an easy option of scheduling tasks which will be triggered when the interval or time is right. We can use the @Scheduled annotation to achieve this, but what if we want to schedule the jobs on certain events, or by invoking some services.\nLets say you are working on a project in which you want to schedule your API\u0026rsquo;s to run at 12:00pm every night and collect the data for the entire day and save it to either in database or in some files. There could be multiple task for which you will invoke other API\u0026rsquo;s such as data collection for specific scenarios, or sending notification or updating the status of certain entity when certain event happened etc.\nIn these scenarios the usual @Scheduled annotation will not be very useful, and you might need some dynamic scheduling which can be triggered on the users or customers need.\n@Scheduled #Lets see how the usual way of scheduling works. In your spring boot project, you want to schedule certain task which is to be executed, let\u0026rsquo;s say after every minute, then we can create a service class which will have the method to perform the task and use the @Scheduled annotation, that can be provided with certain arguments\nArguments for @Scheduled e.g. cron with every 10 seconds\n@Scheduled(cron = \u0026#34;0/10 * * * * *\u0026#34;, zone = \u0026#34;Asia/Kolkata\u0026#34;) public void task(){ System.out.println(\u0026#34;Every 10 seconds\u0026#34;); } e.g. fixed delay of 10 seconds\n@Scheduled(fixedDelay = 10000) public void task(){ System.out.println(\u0026#34;Every 10 seconds\u0026#34;); } e.g. fixed rate of 10 seconds\n@Scheduled(fixedRate = 10000) public void task(){ System.out.println(\u0026#34;Every 10 seconds\u0026#34;); } Or you can use the parameterized string, which will take value from *.properties or * yml file.\n@Scheduled(fixedDelayString = \u0026#34;${fixedDelay.in.millisec}\u0026#34;) public void task(){ System.out.println(\u0026#34;Every 10 seconds\u0026#34;); } Similarly, fixedRateString and cron expression can also be used.\nAlso, you need to add the @EnableScheduling on the Scheduler Configuration file or on the Main class where @SpringBootApplication is used.\nTask Scheduler #Till now, we were talking about the static scheduling where the user cannot do anything once the application is started. But in normal day to day scenario, we will get tasks which should be maintained by either the user or some events.\nIn these scenarios, Spring introduced TaskScheduler interface, which will allow the user to maintain the scheduled jobs and provide the much needed flexibility.\nSo as part of this blog post, we will create a small project which will on request schedule a Job. This job can be used to call other GET or POST API\u0026rsquo;s that is exposed in your microservices environment.\nThe project will have following REST endpoints:\nSchedule GET API\u0026rsquo;s: Type: POST endpoint: /scheduleGet payload: Job description: It will allow user to schedule the Rest API\u0026rsquo;s of GET type. Schedule POST API\u0026rsquo;s: Type: POST endpoint: /schedulePost payload: Job description: It will allow user to schedule the Rest API\u0026rsquo;s of POST type. Get all scheduled Jobs: Type: GET endpoint: /scheduledJobs description: It will allow user to see all the scheduled jobs in this application. Delete any scheduled Jobs: Type: DELETE endpoint: /deleteJob/{jobId} description: It will allow user to delete any scheduled job running in this application. Job #@Data @AllArgsConstructor @NoArgsConstructor @ToString public class Job { private String jobName; private String apiURL; private String baseURL; private String cron; } This is the payload of the endpoints, which will take the base URL, the API URL which are exposed and the cron expression. Job name is also mandatory to recognize the job.\nScheduler Configuration #Since we are not using the @Scheduled annotation, we have to create the scheduler configuration which will take the SchedulingConfigurer interface. The interface will allow to configure the task by providing a overridden method configureTasks().\nAt this stage, we need the TaskScheduler to initialize the scheduler and provide what kind of scheduler we are going to use. We can also define the thread scheduler name and the pool size.\nPool size is useful in case we have multiple scheduled jobs clashing to run at same time. If we have multiple threads, then it can be assigned to multiple threads and executed, else if we have only one thread, then the other scheduled job need to wait for it to finish.\nSo finally my scheduler config class will look like this:\n@Configuration @EnableScheduling @ConditionalOnProperty(name = \u0026#34;spring.scheduler.enabled\u0026#34;) public class SchedulerConfiguration implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setTaskScheduler(taskScheduler()); } @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(poolSize); scheduler.setThreadNamePrefix(\u0026#34;ThreadScheduler-\u0026#34;); scheduler.initialize(); return scheduler; } } If you have noticed, we have used the @ConditionalOnProperty annotation which will take a property that can be set in the application.properties or yaml file. In this case, it will either enable or disable the scheduler we are creating.\nAt this point, I have the scheduler configuration set. So, if I call taskScheduler.schedule() method with the Runnable task and cron expression, we are done with scheduling a task.\nFrom here, we will take further to invoke an API which is defined in the payload (Job). When we send a request for scheduling any GET API\u0026rsquo;s, the scheduler application will call your GET API following the cron expression passed as payload.\nThe scheduledGet API looks like this:\n@PostMapping(\u0026#34;/scheduleGet\u0026#34;) public void scheduleGetJob(@RequestBody Job job){ if(null!=job){ schedulerService.scheduleGetJob(job); } } All the scheduled jobs are maintained in a Map.\nprivate Map\u0026lt;String, Job\u0026gt; schedulerMap = new HashMap\u0026lt;\u0026gt;(); public void scheduleGetJob(Job job) { log.info(\u0026#34;scheduling get job:\u0026#34;+ job.toString()); taskScheduler.schedule( () -\u0026gt; { ResponseEntity\u0026lt;String\u0026gt; response = webClient.get() .uri(job.getBaseURL() + job.getApiURL()) .header(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) .retrieve() .toEntity(String.class) .block(); if(response.getStatusCode().isError()){ log.error(\u0026#34;Failure calling Job:\u0026#34;+ job.getJobName()+ \u0026#34;with URL:\u0026#34;+ job.getBaseURL()+job.getApiURL()); } else { log.info(response.getBody()); } }, new CronTrigger(job.getCron(), TimeZone.getTimeZone(TimeZone.getDefault().toZoneId())) ); schedulerMap.put(job.getJobName() + \u0026#34;-\u0026#34; + System.currentTimeMillis(), job); } The taskScheduler.schedule() takes a Runnable task, and cron trigger. Here my runnable task is to call the GET API defined in the the payload Job.\nIf you notice the request body, we are defining the base and API URL and the cron expression. Once this API is invoked, it will schedule the job \u0026lsquo;test-get\u0026rsquo; which will call the API \u0026lsquo;http://localhost:7887/hello\u0026rsquo; after every 10 seconds.\nI designed the /hello API to print a message\n@GetMapping(\u0026#34;/hello\u0026#34;) public String sayHello(){ return \u0026#34;Ola Amigos!\u0026#34;; } Similarly, for scheduling a POST job, we are calling the POST API which takes Job as the payload. The behavior is similar to that of scheduling the GET API.\nThese are the logs for the two scheduled Jobs\nAlso, we can get all the scheduled jobs using this API, and\nDelete any specific scheduled job using this API.\nHoping this small project will help someone who is looking for dynamic scheduling in spring boot projects. You can find the entire code on my Github. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\nReferences # https://riteshshergill.medium.com/dynamic-task-scheduling-with-spring-boot-6197e66fec42* * https://crontab.cronhub.io/ ","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/dynamic-scheduling-any-rest-api-s/","section":"Posts","summary":"\u003cp\u003eSpring boot provides an easy option of scheduling tasks which will be triggered when the interval or time is right. We can use the @Scheduled annotation to achieve this, but what if we want to schedule the jobs on certain events, or by invoking some services.\u003c/p\u003e\n\u003cp\u003eLets say you are working on a project in which you want to schedule your API\u0026rsquo;s to run at 12:00pm every night and collect the data for the entire day and save it to either in database or in some files. There could be multiple task for which you will invoke other API\u0026rsquo;s such as data collection for specific scenarios, or sending notification or updating the status of certain entity when certain event happened etc.\u003c/p\u003e","title":"Dynamic Scheduling any REST API's"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/","section":"DynamicallyBluntTech","summary":"","title":"DynamicallyBluntTech"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/spring/","section":"Categories","summary":"","title":"Spring"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/spring/","section":"Tags","summary":"","title":"Spring"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/spring-boot/","section":"Categories","summary":"","title":"Spring Boot"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/spring-boot/","section":"Tags","summary":"","title":"Spring Boot"},{"content":"In the days of Vulnerabilities, the famous Spring framework is the latest victim, which has quite a turns of event for the CVE identified as CVE-2022-22965. The latest Spring Vulnerabilities was to be reported on 31st March, 2022, but someone has leaked the detailed information online a day before the CVE publication.\nA Zero Day Vulnerability occurs when a software package has a vulnerability that becomes public knowledge and there is no approved patch available to fix it at the time it becomes public.\nCVE-2022-22965 #It is Remote Code Execution(RCE) vulnerability via data binding in the Spring framework, first reported by VMWare by coeplutos, meizhm3i of AntGroup FG.\nRCE or Remote Code Execution is maliciously executing some code fragments which could either harm your application or effect the data integrity of your application.\nThis vulnerability impacts the Spring MVC and *WebFlux *framework running on JDK9+. This specific exploits requires the application to be running on Apache Tomcat server as a WAR deployment. A rather good thing is that the Vulnerability doesn\u0026rsquo;t effect the Spring Boot deployment with Embedded Apache Tomcat Server.\nHowever, Spring blog has quoted:\nHowever, the nature of the vulnerability is more general, and there may be other ways to exploit it that have not been reported yet.\nYour application is impacted if it is\nRunning on JDK 9 or higher, Deployed as WAR using Apache Tomcat as servlet container, Have spring-mvc and spring-webflux as dependency Using Spring framework versions 5.3.0 to 5.3.17 5.2.0 to 5.2.19 and older versions Mitigation #Spring has released the fix in Spring framework versions\n5.3.18 and 5.2.20 For Spring Boot, use the versions 2.6.6 or 2.5.12 which depends on Spring Framework v5.3.18 newly released.\nSuggested Workaround #These workarounds is required, if you are restricted to not upgrade you application to above versions. If you can upgrade than this is workaround is not required.\nAdding to the fix from the leaked reports, Spring has documented a more fail-safe way to implement it.\n*Applications can extend RequestMappingHandlerAdapter to update the WebDataBinder at the end after all other initialization. In order to do that Spring boot application can declare a WebMvcRegistration bean or a WebFluxRegistration bean. *\nFor example, the Spring boot application has to override createDataBinderFactory() to disallow these\nclass.* Class.* .class. .Class. So, now in the *SpringBootApplication *main class, we will add the *webMvcRegistrations() *which returns a new static inner class ExtendedRequestMappingHandlerAdapter.\n@Bean public WebMvcRegistrations webMvcRegistrations(){ return new WebMvcRegistrations() { @Override public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { return new ExtendedRequestMappingHandlerAdapter(); } }; } This *ExtendedRequestMappingHandlerAdapter *will override the createDataBinderFactory() method to disallow the above fields. We have added the extra fields to disallow in the new data binding factory.\nprivate static class ExtendedRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter { @Override protected InitBinderDataBinderFactory createDataBinderFactory( List\u0026lt;InvocableHandlerMethod\u0026gt; methods){ return new ServletRequestDataBinderFactory(methods, getWebBindingInitializer()){ @Override protected ServletRequestDataBinder createBinderInstance( Object target, String name, NativeWebRequest req ) throws Exception { ServletRequestDataBinder binder = super.createBinderInstance(target, name, req); String[] fields = binder.getDisallowedFields(); List\u0026lt;String\u0026gt; fieldList = new ArrayList\u0026lt;\u0026gt;( fields!=null ? Arrays.asList(fields) : Collections.emptyList() ); fieldList.addAll( Arrays.asList( \u0026#34;class.*\u0026#34;, \u0026#34;Class.*\u0026#34;, \u0026#34;*.class.*\u0026#34;, \u0026#34;*.Class.*\u0026#34; ) ); binder.setDisallowedFields( fieldList.toArray(new String[]{}) ); return binder; } }; } } Similarly, for Spring MVC without Spring Boot, application can switch from @EnableWebMvc to extending *DelegatingWebMvcConfiguration *directly.\nSo when we do @EnableWebMvc, it internally calls *DelegatingWebMvcConfiguration, *which:\nProvide default Spring configuration for Spring MVC applications Detects and delegates to *WebMvcConfigurer *implementations to customize that configurations. So, now instead of enabling the annotation, we will directly extend the *DelegatingWebMvcConfiguration *class like this:\n@Configuration public class WebConfig extends DelegatingWebMvcConfiguration { // ... } Now, you can have existing methods in WebConfig, and also override different implementation of *WebConfigurer *on the classpath.\nAfter extending , we need to override the createRequestMappingHandlerAdapter() method.\nThis is the not the first time a heavily used framework was impacted by vulnerabilities. Last time it was Log4j, which took some time to come up with their latest version 2.17.2 without any vulnerabilities reported yet.\nHope you like the content of the blog. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\nReferences # https://tanzu.vmware.com/security/cve-2022-22965* * https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement* * ","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/spring4shell-zero-day-vulnerability-in-spring-framework/","section":"Posts","summary":"\u003cp\u003eIn the days of Vulnerabilities, the famous Spring framework is the latest victim, which has quite a turns of event for the CVE identified as \u003ca href=\"https://tanzu.vmware.com/security/cve-2022-22965\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eCVE-2022-22965\u003c/em\u003e\u003c/a\u003e. The latest Spring Vulnerabilities was to be reported on 31st March, 2022, but someone has leaked the detailed information online a day before the CVE publication.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003eA \u003cstrong\u003e\u003cstrong\u003eZero Day Vulnerability\u003c/strong\u003e\u003c/strong\u003e occurs when a software package has a vulnerability that becomes public knowledge and there is no approved patch available to fix it at the time it becomes public.\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Spring4Shell - Zero Day Vulnerability in Spring Framework"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/zeroday/","section":"Tags","summary":"","title":"Zeroday"},{"content":"In this post we will look into the different ways to traverse a Binary Tree.\nIn a binary tree, you can traverse in two different ways:\nBreadth first traversal Depth first traversal In-order Pre-order Post-order Lets start with an example. Given the following binary tree:\n4 / \\ 2 7 / \\ / \\ 1 3 6 9 When we traverse a binary tree, we can go either breadth first or depth first.\nBreadth First #Now when we say breadth first, it start from the root node and traverse all the nodes at that level. Once all the nodes is traversed, we go to next level and so on.\nIn our case, the breadth first will be like this\n4 --\u0026gt; 2 --\u0026gt; 7 --\u0026gt; 1 --\u0026gt; 3 --\u0026gt; 6 --\u0026gt; 9 Depth First #In the depth first, we traverse from root to the leaf node. There are three types of depth first traversal:\nIn-order Traversal #In this traversal, we follow the order: *Left \u0026gt; Root \u0026gt; Right. *We print the left node then the root and then the right node. So, in our case the In-order traversal will be\n1 --\u0026gt; 2 --\u0026gt; 3 --\u0026gt; 4 --\u0026gt; 6 --\u0026gt; 7 --\u0026gt; 9 Pre-order Traversal #In this traversal, we follow the order: Root \u0026gt;* Left \u0026gt; Right. *We print the left node then the root and then the right node. So, in our case the In-order traversal will be\n4 --\u0026gt; 2 --\u0026gt; 1 --\u0026gt; 3 --\u0026gt; 7 --\u0026gt; 6 --\u0026gt; 9 Post-order Traversal #In this traversal, we follow the order: *Left \u0026gt; Right \u0026gt;Root . *We print the left node then the root and then the right node. So, in our case the In-order traversal will be\n1 --\u0026gt; 3 --\u0026gt; 2 --\u0026gt; 6 --\u0026gt; 9 --\u0026gt; 7 --\u0026gt; 4 These are some of the ways to traverse a binary tree. You can find the code base here. Github Link Also, refer here to know how to inverse a binary tree.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/binary-tree-traversal/","section":"Posts","summary":"\u003cp\u003eIn this post we will look into the different ways to traverse a Binary Tree.\u003c/p\u003e\n\u003cp\u003eIn a binary tree, you can traverse in two different ways:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eBreadth first traversal\u003c/li\u003e\n\u003cli\u003eDepth first traversal\u003c/li\u003e\n\u003cli\u003eIn-order\u003c/li\u003e\n\u003cli\u003ePre-order\u003c/li\u003e\n\u003cli\u003ePost-order\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eLets start with an example. Given the following binary tree:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e4\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e   \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  2     7\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e   \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1   3 6   9\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhen we traverse a binary tree, we can go either breadth first or depth first.\u003c/p\u003e","title":"Binary Tree Traversal"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/binarytree/","section":"Tags","summary":"","title":"BinaryTree"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/cloud/","section":"Categories","summary":"","title":"Cloud"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/cloud/","section":"Tags","summary":"","title":"Cloud"},{"content":"This is a bit embarrassing to start with, but lets say I got this question and was quite dumb stuck on how to write a custom iterator. Seems simple at that point, but couldn\u0026rsquo;t exactly curate the process of developing a custom iterator which would return the second element instead of the next element.\nSo this post will be about Embarrassment.\nLets start with Iterators.\n**Iterator ** #Iterators are something, using which we iterate over a given collection of elements given in Java. So, lets says the list of Strings objects can be iterated over by list.iterator().\nNow we know what an *iterator *is, we should be aware of what *Iterable *interface is.\nIterable Interface #This was introduced in Java 1.5. It makes custom data structure works with the enhanced for-each loop. It has only one method *iterator() *to be implemented.\nSo, we know a bit on Iterable and more on Iterator. These scenarios or questions are more common when you start creating a custom data structure, and think about how to make this easily iterate-able.\nWe will just do the same and without any embarrassment.\nCreate Our Own Data Structure #In this part, we will create a new data structure which is called CustomList. It works like List class, but the iterator implemented in *CustomList *will return me second element on every call.\nThis *CustomList *will implement Iterable of Type T, which will allows us to implement the iterator that goes with this custom data structure. It will work on a List, and utilize the List methods to return the first and last element. And implementation for add() method to insert new element to the list.\nclass CustomList\u0026lt;T\u0026gt; implements Iterable\u0026lt;T\u0026gt; { List\u0026lt;T\u0026gt; list; CustomList(List\u0026lt;T\u0026gt; list) { this.list = list; } public void add(T s) { list.add(s); } public T getFirst() { return list.get(0); } public T getLast() { return list.get(list.size() - 1); } public Iterator\u0026lt;T\u0026gt; iterator() { return new SecondIterator(); } //Inner Class class SecondIterator\u0026lt;T\u0026gt; implements Iterator\u0026lt;T\u0026gt; { . . . . } } As you noticed, the iterator() method is returning a custom iterator called SecondIterator.\nNow here, we have to implement the SecondIterator, such that it returns the second element. And this *SecondIterator *will be the inner class of my *CustomList *data structure.\nSo the next piece of code is for *SecondIterator *which implements the *Iterator *Interface, thus making sure that the custom iterator should have *next() *and hasNext() methods implemented.\nclass SecondIterator\u0026lt;T\u0026gt; implements Iterator\u0026lt;T\u0026gt; { private int current = 0; private int size = list.size(); //Return true if the second element exists @Override public boolean hasNext() { return size - current \u0026gt; 1; } //Return Second element from the list @Override public T next() { if(!hasNext()) throw new NoSuchElementException(); T t = (T) list.get(current); current+=2; return t; } } Here, we need to work on the index, in order to be specific about where the current pointer is.\nSo lets say, I am at the 4th index of my custom data structure, and now if I call next(), it should always give me the 6th index element (because we are implementing *SecondIterator *and not a normal Iterator). And that\u0026rsquo;s why, we will have a variable to refer the correct element, when we call next() on it.\nAlso this makes my job easier for hasNext(), because it doesn\u0026rsquo;t have to check if it is at 2nd index, or 4th index or 6th index etc\u0026hellip;.At any moment, I will check the difference of the actual size of the list and the current index, which should be greater than 1, in that case the hasNext() should always be true, because it will always have the at-least 2 elements pending.\nSimilarly, my next() method will return NoSuchElementException(), if there are no elements present. Else, it will get the value at that current index from the list and increment the current index pointer by 2, to always get the second element from the current index.\nHere, I can create Nth-Iterator also, which will be give me elements on the Nth index from the current index, by just incrementing the value by N (Also, the same changes need to go to the hasNext() method.)\nNow, after all these explanations on this tricky question, which I couldn\u0026rsquo;t do when asked, lets try our new Custom Iterator and find out if it is working or not.\nMain #Here is the Main class which will create a List of Strings, and pass it to the custom data structure. And then iterate using the for-each on the *CustomList *to see if we are getting the second element always from the current index or not.\nSee, here the Iterable interface will come to work.\npublic class Main{ public static void main(String[] args) { List\u0026lt;String\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); list.add(\u0026#34;a\u0026#34;); //1st Element list.add(\u0026#34;b\u0026#34;); list.add(\u0026#34;c\u0026#34;); //Second from 1st Element list.add(\u0026#34;d\u0026#34;); list.add(\u0026#34;e\u0026#34;); //Second from 3rd Element list.add(\u0026#34;f\u0026#34;); list.add(\u0026#34;g\u0026#34;); //Second from 5th Element list.add(\u0026#34;h\u0026#34;); CustomList\u0026lt;String\u0026gt; customList = new CustomList(list); for(String s: customList){ System.out.println(s); } } } Now, when we executed the enhanced for-each loop, it didn\u0026rsquo;t threw any error and was able to print the second element as was implemented in the custom iterator.\nThis is such a good question to learn how the iteration over the collection works. Also, I would recommend to try it out yourself, somewhere we all will get stuck, but then that\u0026rsquo;s how you will find how good this question is. Also, it will help in avoiding the embarrassment phase and a curious night over a blog post.\nHope this post will bring a smile and lots of learning. You can refer to my Github Repository for the full code base of this example.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/custom-iterators-in-java/","section":"Posts","summary":"\u003cp\u003eThis is a bit embarrassing to start with, but lets say I got this question and was quite dumb stuck on how to write a custom iterator. Seems simple at that point, but couldn\u0026rsquo;t exactly curate the process of developing a custom iterator which would return the second element instead of the next element.\u003c/p\u003e\n\u003cp\u003eSo this post will be about \u003cem\u003e\u003cstrong\u003eEmbarrassment\u003c/strong\u003e\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eLets start with Iterators.\u003c/p\u003e\n\u003ch3 id=\"iterator-\" class=\"relative group\"\u003e**Iterator ** \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#iterator-\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eIterators are something, using which we iterate over a given collection of elements given in Java. So, lets says the list of Strings objects can be iterated over by \u003cem\u003elist.iterator()\u003c/em\u003e.\u003c/p\u003e","title":"Custom Iterators in Java"},{"content":"In this post, we will discuss on fetching the Spring boot configuration values from outside the application jar file. We will discuss on dockerizing the application which will take the property values from an external configuration file. We will also use different Profiles to get the message based on the profile we choose.\nWe have also discussed on Spring boot configuration files and how to get the values from it in this post spring-boot-application-external-configurations.\nTo start with, we will need a Spring boot application, which will have one endpoint /api/message to get the message from the spring configuration file. To get the spring boot application, you can directly go to https://start.spring.io/ website and create a spring project with maven and choose the Spring Web dependency. It is a small demonstration where no other dependency is required. Spring Initializr Profile #Now that we have the spring boot application which will have the required dependencies, we have to create different maven profile and respective application-{profile}.properties files under the resources directory. Project Structure pom.xml To add the maven profile, we need to add the following profile tag in the pom.xml file.\nWe have two profiles dev and *cloud *and marked the dev profile as the default profile.\nThe application.properties file will define the active profile.\nserver.port=9898 spring.profiles.active=@activatedProperties@ dbt.message=DynamicallyBluntTech The spring.profiles.active will have the variable (activatedProperties) which will be getting the value from external parameter defined by user during the startup.\nSimilarly, the other profile configuration file will have different message for the key dbt.message*.*\napplication-cloud.properties #dbt.message=Cloud-DBTech application-dev.properties #dbt.message=Dev-DBTech Controller #The Rest controller will have one endpoint which will get the value for this dbt.message key. Controller Once the controller is ready, we can start our spring boot application, which will take the dev profile as the default to start with. And the call to /api/message will also fetch the dbt.message value from application-dev.properties file. Active Profile And the API endpoint call will give this Response Now, lets the run the spring boot application as jar file with the active profile set to cloud Cloud Profile And now the API endpoint call will give this Response Till this point we were getting the values from the configuration values based on the profile which we are choosing during the startup of the project. If we define the cloud profile, the values present in the cloud configuration file is fetched.\nNow lets say we are working on the project, where we want to spawn multiple instance of the same application, but with different messages for each instances. This could be different database connection parameters or different Kafka topics to consume data from.\nHow would we take the configurable parameters during the startup of the application dynamically ?\nDockerize #So lets first create our Dockerfile which will push the artifact to the docker hub. Docker We are\ntaking the openjdk version 11, and marking /dbt as the work directory Copying the application jar file to the docker container with the name application.jar Exposing the port 9898 for accessing application outside the docker container. Defining the command to execute the jar file by passing the value of the dbt.message key. By doing this, we are just overriding the specific configuration parameter key while starting the application. But lets say I have bunch of parameters that needs to be changed for each instance spawned. Adding all these parameters in the ENTRYPOINT command will make it more confusing and will become error prone.\nIn these scenarios, we can also create the profile specific configuration file where the jar file is added. And then add the respective active profile in the run command. So, now the Dockerfile will look like this Docker This will override the application-cloud.properties configuration present in the application.jar file by the application-cloud.properties file we copied.\nWe will build and push the docker image using the above Dockerfile\ndocker build -f .\\src\\main\\java\\dockerize\\Dockerfile -t dockerize:tb1 Docker Build Logs And run the docker image dockerize:tb1 on the port 9898\ndocker run -p 9898:9898 dockerize:dbt1 Logs The endpoint from the docker image will fetch the data for dbt.message key from the value set in the Dockerfile Response Now lets answer this question:\nQ\u0026gt; How would we take the configurable parameters during the startup of the application dynamically ?\nA\u0026gt; We can use the configuration file or we can use specific property in the Entrypoint command of our Dockerfile to pass the values during the docker run.\nYou can find the code in my Github repo. Hope this helps in understanding the external configuration of the spring boot application and how we can override the configuration specific values or the entire configuration file.\nThis was more on the static configuration of the Spring boot application, because once the application is started, you cannot change the configuration parameters and its values. To change it during the runtime, Spring has provided Spring Cloud configuration which will fetch the configuration values from the external files stored in some cloud repository like Github or Gitlab, etc. More on this in the next post of the external configuration of Spring boot applications.\nHope this will help you in your learning process. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/externalize-spring-boot-configuration/","section":"Posts","summary":"\u003cp\u003eIn this post, we will discuss on fetching the Spring boot configuration values from outside the application jar file. We will discuss on dockerizing the application which will take the property values from an external configuration file. We will also use different Profiles to get the message\nbased on the profile we choose.\u003c/p\u003e\n\u003cp\u003eWe have also discussed on Spring boot configuration files and how to get the values from it in this post \u003ca href=\"https://www.dynamicallyblunttech.com/post/spring-boot-application-external-configurations\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003espring-boot-application-external-configurations\u003c/em\u003e\u003c/a\u003e\u003cem\u003e.\u003c/em\u003e\u003c/p\u003e","title":"Externalize Spring Boot Configuration"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/interview/","section":"Categories","summary":"","title":"Interview"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/iterators/","section":"Tags","summary":"","title":"Iterators"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/java/","section":"Categories","summary":"","title":"Java"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/java/","section":"Tags","summary":"","title":"Java"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/mongodb/","section":"Categories","summary":"","title":"MongoDB"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/mongodb/","section":"Tags","summary":"","title":"MongoDB"},{"content":"In this post we will discuss one of the issue encountered after installing MongoDB on my windows machine. This from the previous post where we performed all the CRUD operation on MonogDB in Java.\nFor installation of MongoDB, follow the official guide.\nIssue #While opening the mongo.exe by double clicking on it, the console opens and then it vanishes. So, I tired again, still the console just showed for a second or two and then vanished again in thin air. Even tried using the mongoDB compass editor to check the connection, but that also didn\u0026rsquo;t connect.\nReason #So this issue can happen if the underline data directory is corrupted or the mongoDB needs some repairing.\nCheck for data directory you have started MongoDB with, or you can create another data directory. Mostly if you are using a different data directory, that should solve the issue, but if there is something to be repaired on the MongoDB, then this will again occur, when you again open it.\nResolution #As stated above, try opening with a different data directory like this\nmongod --dbpath C:/data/db If the data/db directory is not present, you can create it under C: directory. You can also use any other directory and mention the path here.\nIf creating the db path also didn\u0026rsquo;t solve the problem, then you have to repair the installed MongoDB.\nGo to the installed bin directory and run following command:\nmongod --repair Let this run, it shouldn\u0026rsquo;t take much time. When the repairing is done, it will not close the console and you also shouldn\u0026rsquo;t close it.\nNow, double click on the mongo.exe and the console will not vanish and you can key in your commands.\nThis helped me resolving the above issue. Let me know in the comments below if you followed another resolution technique. Also if you like my work you can subscribe and appreciate my work from the Subscribe section.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/target-machine-actively-refused-it-mongodb-issue/","section":"Posts","summary":"\u003cp\u003eIn this post we will discuss one of the issue encountered after installing MongoDB on my windows machine. This from the \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/crud-operation-in-mongodb/\"\u003e\u003cem\u003e\u003cstrong\u003eprevious post\u003c/strong\u003e\u003c/em\u003e\u003c/a\u003e where we performed all the CRUD operation on MonogDB in Java.\u003c/p\u003e\n\u003cp\u003eFor installation of MongoDB, follow the official guide.\u003c/p\u003e\n\u003ch3 id=\"issue\" class=\"relative group\"\u003e\u003cstrong\u003eIssue\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#issue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eWhile opening the mongo.exe by double clicking on it, the console opens and then it vanishes. So, I tired again, still the console just showed for a second or two and then vanished again in thin air. Even tried using the mongoDB compass editor to check the connection, but that also didn\u0026rsquo;t connect.\u003c/p\u003e","title":"Target Machine Actively Refused it - MongoDB Issue"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/apache/","section":"Categories","summary":"","title":"Apache"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/apache/","section":"Tags","summary":"","title":"Apache"},{"content":"Referring to the Spring Guide to Spring security for authenticating a user with LDAP, this blog is to create the working example for someone to try it out.\nThe example present as part of the guide is having some configuration missing, without which the example won\u0026rsquo;t work. I got to know from some Stackoverflow pages about the missing configurations and added the correct form to this post.\nDependencies #I am using Maven for my project and it requires following external libraries for enabling the LDAP authentication in our spring project.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-security\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.ldap\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-ldap-core\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.security\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-security-ldap\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.unboundid\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;unboundid-ldapsdk\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Web Security Configurer Adapter #In order to get the LDAP authentication working, we have to add another configuration which will extend WebSecurityConfigurerAdapter class. The configure()* ***method will be overridden to manually add some additional configuration for the login works in your spring project. The *WebSecurityConfigurerAdapter *class implements *WebSecurityConfigurer *interface.\nIt also provides an embedded LDAP server with default configuration. We will use ldapauthentication() method which will configure things so that the username in the login form is plugged into {0} such that it searches below pattern in the LDAP server\nuid={0},ou=people,dc=springframework,dc=org In the flow, the configure method with *AuthenticationManagerBuilder *class as argument will be called first and after that it will call the other configure method with *HttpSecurity *method.\n@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.ldapAuthentication() .userDnPatterns(\u0026#34;uid={0},ou=people\u0026#34;) .groupSearchBase(\u0026#34;ou=groups\u0026#34;) .contextSource() .url(\u0026#34;ldap://localhost:8389/dc=springframework,dc=org\u0026#34;) .and() .passwordCompare() // .passwordEncoder(new BCryptPasswordEncoder()) .passwordAttribute(\u0026#34;userPassword\u0026#34;); } NOTE: In the above configure method, keep the *passwordEncoder *line commented, else the user authentication will not work. It was throwing user authentication failed when we enable the *BCryptPasswordEncoder *class.\nAfter the above configure method the below configure method will be called\n@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .fullyAuthenticated() .and() .formLogin(); } LDAP Data Interchange Format file #The ldif file contains the user repository for the users which are allowed to access the application with respective users details, groups and other permissions.\nRefer to the file here: test-server.ldif\nConfigurations in application.properties #If you run the spring guide example as is, you will get an application error on the runtime that the root 389 connection refused error.\n2021-11-09 22:52:41.170 INFO 5760 --- [-192.168.225.52] o.s.web.servlet.DispatcherServlet : Initializing Servlet \u0026#39;dispatcherServlet\u0026#39; 2021-11-09 22:52:41.182 INFO 5760 --- [-192.168.225.52] o.s.web.servlet.DispatcherServlet : Completed initialization in 12 ms 2021-11-09 22:52:41.865 WARN 5760 --- [-192.168.225.52] o.s.b.actuate.ldap.LdapHealthIndicator : LDAP health check failed org.springframework.ldap.CommunicationException: localhost:389; nested exception is javax.naming.CommunicationException: localhost:389 [Root exception is java.net.ConnectException: Connection refused: connect] at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:108) ~[spring-ldap-core-2.3.3.RELEASE.jar:2.3.3.RELEASE] at org.springframework.ldap.core.support.AbstractContextSource.createContext(AbstractContextSource.java:355) ~[spring-ldap-core-2.3.3.RELEASE.jar:2.3.3.RELEASE] at org.springframework.ldap.core.support.AbstractContextSource.getReadOnlyContext(AbstractContextSource.java:164) ~[spring-ldap-core-2.3.3.RELEASE.jar:2.3.3.RELEASE] In order to resolve these errors we need to add the following configurations:\nspring.ldap.embedded.port=8389 spring.ldap.embedded.ldif=classpath:test-server.ldif spring.ldap.embedded.base-dn=dc=springframework,dc=org The port for ldap server is mentioned with the spring.ldap.emebedded.port key and\nthe ldif file location is in classpath is given with spring.ldap.embedded.ldif key and\nthe root DN is given by the last key spring.ldap.embedded.base-dn\nOnce the above configuration is added, then on application startup, the browser will show a login page with /login URL and it will only accept users given as part of the test-server.ldif file.\nLogin Page You can find the complete code base in Github here.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/authentication-with-ldap-in-spring-project/","section":"Posts","summary":"\u003cp\u003eReferring to the \u003ca href=\"https://spring.io/guides/gs/authenticating-ldap/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eSpring Guide\u003c/em\u003e\u003c/a\u003e to Spring security for authenticating a user with LDAP, this blog is to create the working example for someone to try it out.\u003c/p\u003e\n\u003cp\u003eThe example present as part of the guide is having some configuration missing, without which the example won\u0026rsquo;t work. I got to know from some Stackoverflow pages about the missing configurations and added the correct form to this post.\u003c/p\u003e\n\u003ch2 id=\"dependencies\" class=\"relative group\"\u003eDependencies \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#dependencies\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eI am using Maven for my project and it requires following external libraries for enabling the LDAP authentication in our spring project.\u003c/p\u003e","title":"Authentication With LDAP in Spring"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/executor/","section":"Categories","summary":"","title":"Executor"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/executor/","section":"Tags","summary":"","title":"Executor"},{"content":"In this series of learning Natural Language Processing with Apache OpenNLP library in Java, we will discuss on NamedEntityRecognition, which can identify names, places, locations etc. in any given string.\nIn Apache OpenNLP library, NamedEntityRecognition uses predefined trained models which is used to recognize names or date or places or location etc. in the given string. These models are:\nen-nerdate.bn, en-ner-location.bin en-ner-organization.bin en-ner-person.bin en-ner-time.bin Like in previous posts on SentenceDetector*** ***and Tokenization, here also we have to identify the models which we want to use, load the model, and use the find method of the ***NameFinderME ***class to find the specific name, location or place as per the models used.\nLets start with loading the person model:\nInputStream is = new FileInputStream(\u0026#34;src\\\\main\\\\resources\\\\models\\\\en-ner-person.bin\u0026#34;); TokenNameFinderModel model = new TokenNameFinderModel(is); The model is loaded in ***TokenNameFinderModel ***class.\nUse this model object to create ***NameFinderME ***object. The find method of this class will take an array of string as input which will return an array of Span object. This array will have all the matching names, places or location etc as per the models used.\nNameFinderME finder = new NameFinderME(model); String[] words = text.split(\u0026#34; \u0026#34;); Span[] spans = finder.find(words); Use the Span\u0026rsquo;s getStart() and getEnd() methods to get the indexes of the matching words.\nSo for an input like this:\nNamedEntityRecognition ner = new NamedEntityRecognition(); String sentence = \u0026#34;Indian Names could either Abhay or Ankit and other options are Charles and Michael\u0026#34;; ner.nameFinder(sentence); This will be the output:\n[11..12) person---Charles [13..14) person---Michael If you want to find the places, location or time, then use the respective models.\nLimitation of these Models: #The models for names *en-ner-person.bin *will work on a small subset of the name, as I tried with Indian names as input, but it only recognizes the western names. This also is limited to some names.\nFor the entire codebase, you can visit my Github repository.\nReferences\nhttps://opennlp.apache.org/docs/1.9.2/manual/opennlp.html https://opennlp.apache.org/docs/1.9.2/apidocs/opennlp-tools/index.html https://www.tutorialspoint.com/opennlp/index.htm http://opennlp.sourceforge.net/models-1.5/ Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/iii-named-entity-recognition-apache-opennlp/","section":"Posts","summary":"\u003cp\u003eIn this series of learning Natural Language Processing with \u003cem\u003e\u003cstrong\u003eApache OpenNLP\u003c/strong\u003e\u003c/em\u003e library in Java, we will discuss on \u003cem\u003e\u003cstrong\u003eNamedEntityRecognition\u003c/strong\u003e\u003c/em\u003e, which can identify names, places, locations etc. in any given string.\u003c/p\u003e\n\u003cp\u003eIn \u003cem\u003e\u003cstrong\u003eApache OpenNLP\u003c/strong\u003e\u003c/em\u003e library, \u003cem\u003e\u003cstrong\u003eNamedEntityRecognition\u003c/strong\u003e\u003c/em\u003e uses predefined trained models which is used to recognize names or date or places or location etc. in the given string. These models are:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cem\u003een-nerdate.bn,\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003een-ner-location.bin\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003een-ner-organization.bin\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003een-ner-person.bin\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003een-ner-time.bin\u003c/em\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eLike in previous posts on \u003ca href=\"https://dynamicallyblunttech.blogspot.com/2020/07/find-sentences-using-apache-open-nlp.html\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eSentenceDetector\u003c/em\u003e\u003c/a\u003e\u003ca href=\"https://dynamicallyblunttech.blogspot.com/2020/07/find-sentences-using-apache-open-nlp.html\" target=\"_blank\" rel=\"noreferrer\"\u003e*** ***\u003c/a\u003eand \u003ca href=\"https://dynamicallyblunttech.blogspot.com/2020/07/tokenization-using-apache-opennlp.html\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eTokenization\u003c/em\u003e\u003c/a\u003e, here also we have to identify the models which we want to use, load the model, and use the find method of the ***NameFinderME ***class to find the specific name, location or place as per the models used.\u003c/p\u003e","title":"III - Named Entity Recognition - Apache OpenNLP"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/ldap/","section":"Tags","summary":"","title":"LDAP"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/nlp/","section":"Categories","summary":"","title":"NLP"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/opennlp/","section":"Tags","summary":"","title":"OpenNLP"},{"content":"In this post we will move all files from one directory to another using Java\u0026rsquo;s Executor Service and will also discuss on how we can enhance the entire process.\nTo move files from one directory to another:\nList all files in the source directory Create a main class which will call the threads to process all the files in given directory. So lets start developing our main class which will hold the skeleton of the program to call executor thread which will in turn transfer all files from one directory to another.\n**Main ** # Checks if the source and target directory exists Get the stream of Path using Files.walk() on the source directory. For each file in the Stream of paths, it puts an entry into the *ConcurrentHashMap *declared globally. Calls the method which will in-turn call the thread using Executor Service. private static final Integer THREADS = 10; public static ConcurrentHashMap\u0026lt;String, Integer\u0026gt; filesMap = new ConcurrentHashMap\u0026lt;\u0026gt;(); public static void main(String[] args) { File workDir = new File(\u0026#34;src\\\\main\\\\resources\\\\source\u0026#34;); if(workDir.exists() \u0026amp;\u0026amp; workDir.isDirectory()) { try { Stream\u0026lt;Path\u0026gt; paths = Files.walk(Paths.get(\u0026#34;src\\\\main\\\\resources\\\\source\u0026#34;), 1); paths.forEach(path -\u0026gt; { if(!path.toFile().getName().equalsIgnoreCase(\u0026#34;source\u0026#34;)) filesMap.put(path.toFile().getName(), 0); }); } catch (IOException e) { e.printStackTrace(); } File collectDir = new File(\u0026#34;src\\\\main\\\\resources\\\\target\u0026#34;); if (collectDir.exists() \u0026amp;\u0026amp; collectDir.isDirectory()) { MainClass.moveUsingMultiThread(); } } } Executor Method # Set a fixed thread pool using newFixedThreadPool() method of the Executors class. It creates five ProcessFiles threads and executes it using ExecutorService object. It calls shutdown() of *ExecutorService *once all the thread execution is done. It will wait using the while loop, until all the threads are completed and then print the message \u0026ldquo;Finished all threads\u0026rdquo;. public static void moveUsingMultiThread(){ System.out.println(\u0026#34;Running MultiThreaded Application to Move files from source to target\u0026#34;); ExecutorService executorService = Executors.newFixedThreadPool( THREADS); for(int i=0; i\u0026lt;5; i++) { Runnable worker = new ProcessFiles(); executorService.execute(worker); } executorService.shutdown(); while (!executorService.isTerminated()) { // Wait until all threads are finished } System.out.println(\u0026#34;Finished all threads\u0026#34;); } Executor Thread #This is my Runnable thread which\nIterate over the ConcurrentHashMap, Move the files from source to target using Files.move(), and Remove the entry from ConcurrentHashMap. if(null!= MainClass.filesMap \u0026amp;\u0026amp; MainClass.filesMap.size()\u0026gt;0){ for (Map.Entry\u0026lt;String, Integer\u0026gt; entry :MainClass.filesMap.entrySet()){ synchronized (this) { System.out.println(Thread.currentThread().getName() +\u0026#34;---\u0026#34; + entry.getKey() +\u0026#34;---\u0026#34; + MainClass.filesMap.get(entry.getKey())); try { //Multiple checks for multi threads if (null != MainClass.filesMap \u0026amp;\u0026amp; null!=MainClass.filesMap.get(entry.getKey()) \u0026amp;\u0026amp; MainClass.filesMap.get(entry.getKey()) == 0) { Files.move( Paths.get( \u0026#34;src\\\\main\\\\resources\\\\source\u0026#34; + File.separator + entry.getKey() ), Paths.get( \u0026#34;src\\\\main\\\\resources\\\\target\u0026#34; + File.separator + entry.getKey() ) ); MainClass.filesMap.remove(entry.getKey()); } } catch (NoSuchFileException nsfe) { if (!new File(\u0026#34;src\\\\main\\\\resources\\\\target\u0026#34; + File.separator + entry.getKey()).exists()) { nsfe.printStackTrace(); } } catch (NullPointerException npe){ //There might be a case when some threads will // try to access get on fileMap, // which will be empty at that time. // Hence, on NPE if the map is not empty, then // throw error, else skip if(null!=MainClass.filesMap \u0026amp;\u0026amp; MainClass.filesMap.size()\u0026gt;0){ System.out.println(\u0026#34;FileName:\u0026#34;+entry.getKey() +\u0026#34; Value:\u0026#34;+entry.getValue()); npe.printStackTrace(); } } The *NoSuchFileException *mentioned above is handled in the catch block.\nThis will move all the files from source to target directory using 5 *ProcessFiles *threads and a thread pool size of 10 threads.\nWhile trying this out, we came to a interesting aspect, where the entire list of files can be divided into multiple list (equal to the number of threads) and then passing the sub-list to each thread. By using this logic, you can avoid all the collision because each thread will work on different set of files.\nLimitations # While using the above logic, there are chances that one thread is accessing one file while other thread has already moved it. So, we will get NoSuchFileException. While moving the files, there could be a case where one thread accessed one file from the directory, but another thread moved the same file in the target directory. In these cases, we can encounter NullPointerException. Resolutions # For NoSuchFileException, we can check if the same file exists in the target directory, and if not then throw the error. For NullPointerException, we will keep a Map (in this case a ConcurrentHashMap) to keep the records of the files existing in the directory. And as the threads move the files to target directory, we will remove the entry from the Map. Fixing the Limitations #So in order to fix the limitations, the *ProcessFiles *runnable thread need to work on a limited number of files. And when I say limited number of files, each thread will work on a segment of the list of files collected from the source directory.\nThis is where the List partition comes into picture. As part of Main class, now we will partition the file list into multiple list (in our case, 3 sub-list will be created from the main list of files).\nThe *ProcessFiles *is now changed to accept a list of file as input parameter and we are using submit() of the *ExcecutorService *which can return a Future object.\nList\u0026lt;List\u0026lt;File\u0026gt;\u0026gt; partitionedLists = partitionListOfFiles(fileList, 3); for(List\u0026lt;File\u0026gt; files : partitionedLists) { Runnable worker = new ProcessFilesUsingListPartition(files); executorService.submit(worker); } The partition of list is done using the *subList *method of the List interface:\nprivate static List\u0026lt;List\u0026lt;File\u0026gt;\u0026gt; partitionListOfFiles(List\u0026lt;File\u0026gt; files, final int breakInto) { List\u0026lt;List\u0026lt;File\u0026gt;\u0026gt; fileParts = new ArrayList\u0026lt;\u0026gt;(); final int totalFiles = files.size(); for (int i = 0; i \u0026lt; files.size(); i += breakInto) { fileParts.add( new ArrayList\u0026lt;\u0026gt;(files.subList( i, Math.min(totalFiles, i + breakInto)) ) ); } return fileParts; } Here, lets say you have 10 files and you are partitioning the list into 2 parts, then *breakInto *parameter will be 5.\nThis will actually speed up the task of moving the files from one directory to another. You can vary the size of sub-list of files, based on the number of files you have. So that the program will use same amount of threads as the number of partitioned list of files.\nYou can find the whole code base here in my Github repo ExecutorServiceExample.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/transfer-files-with-executor-service/","section":"Posts","summary":"\u003cp\u003eIn this post we will move all files from one directory to another using Java\u0026rsquo;s Executor Service and will also discuss on how we can enhance the entire process.\u003c/p\u003e\n\u003cp\u003eTo move files from one directory to another:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eList all files in the source directory\u003c/li\u003e\n\u003cli\u003eCreate a main class which will call the threads to process all the files in given directory.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSo lets start developing our main class which will hold the skeleton of the program to call executor thread which will in turn transfer all files from one directory to another.\u003c/p\u003e","title":"Transfer Files with Executor Service "},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/a9fd6a2d-05b1-4b66-838e-4f1065e88f13/","section":"Tags","summary":"","title":"\"A9fd6a2d-05b1-4b66-838e-4f1065e88f13\""},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/e513129b-8816-4fcf-8daa-898b1ece358a/","section":"Tags","summary":"","title":"\"E513129b-8816-4fcf-8daa-898b1ece358a\""},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/f1ddb2ac-31fc-41e0-b0c2-5160c38fe3e8/","section":"Tags","summary":"","title":"\"F1ddb2ac-31fc-41e0-B0c2-5160c38fe3e8\""},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/f74a0531-1cd6-456a-b176-809419144b36/","section":"Tags","summary":"","title":"\"F74a0531-1cd6-456a-B176-809419144b36\"]"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/677b3d25-60e1-4a91-8555-c0ae8d3a7c60/","section":"Tags","summary":"","title":"[\"677b3d25-60e1-4a91-8555-C0ae8d3a7c60\""},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/","section":"Categories","summary":"","title":"[]"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/ai/","section":"Categories","summary":"","title":"AI"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/ai/","section":"Tags","summary":"","title":"AI"},{"content":"I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. #Where to Register: https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\nTopics Covered # agentic vs workflow vs conversational systems, the agentic loop (gather context → act → verify), task decomposition, sequential vs parallel execution, multi-agent orchestration (hub-and-spoke, coordinator/subagent patterns), hooks vs prompts, session resumption, and common anti-patterns. Unofficial practice set, written to match publicly reported blueprint topics — not sourced from the real exam bank. Use for concept drilling, not as a guarantee of exam content.\nSection A: Agentic Systems vs Simpler Patterns #Agentic System: A system where Claude decides its own next action based on what happened before, instead of following a fixed script. The number of steps isn\u0026rsquo;t known in advance — Claude figures it out as it goes. #Think: \u0026ldquo;Go find out X\u0026rdquo; (agentic) vs. \u0026ldquo;Do step 1, then step 2, then step 3\u0026rdquo; (not agentic).\n***Workflow (vs Agent): ***A workflow is a fixed, predictable sequence of steps written by a human. Claude might be used inside one step (like classifying something), but Claude doesn\u0026rsquo;t decide the overall structure. #Rule of thumb: If you can draw the exact steps on a whiteboard beforehand, it\u0026rsquo;s a workflow, not an agent.\n***Conversational System: ***The simplest pattern — a back-and-forth chat with no autonomous tool use or looping. One prompt in, one answer out. #Q1. A support workflow always follows the same 4 steps: classify ticket → fetch order → apply refund rule → send templated reply. Which architecture fits best?\nA) Fully autonomous agent with open-ended tool access\nB) A deterministic workflow with Claude used only at the classification step\nC) Multi-agent hub-and-spoke system\nD) A single conversational session with no tools\nAnswer: B — Fixed, predictable steps don\u0026rsquo;t need agentic autonomy. Reserve agentic loops for tasks with unpredictable branching or unknown step count.\nQ2. Which property is NOT a defining trait of an agentic system?\nA) The model decides its own next action based on tool results\nB) The number of steps is unknown in advance\nC) Output is generated in a single forward pass with no tool calls\nD) The loop continues until a task-completion condition is met\nAnswer: C — that describes a plain conversational/completion pattern, not an agentic one.\nQ3. A team wants Claude to research a topic across an unknown number of sources and compile a report. What\u0026rsquo;s the strongest justification for an agentic (vs workflow) approach here?\nA) Agentic systems are always cheaper\nB) The number and choice of sources can\u0026rsquo;t be predicted ahead of time, so the model must decide dynamically\nC) Workflows can\u0026rsquo;t call tools\nD) Agentic systems don\u0026rsquo;t require verification steps\nAnswer: B.\nSection B: The Agentic Loop (Gather Context → Take Action → Verify) #The Agentic Loop #The core cycle every agentic system runs through, repeatedly, until done:\nGather context – collect the info needed (read a file, call an API, search)\nTake action – do something (call a tool, make a change)\nVerify – check if that action actually worked / got closer to the goal\nRepeat, or stop if the goal is met.\nKey idea: this loop should keep going until a real, checkable finish line is hit — not until Claude just \u0026ldquo;feels\u0026rdquo; done.\nQ4. In a well-formed agentic loop, what should determine when the loop terminates?\nA) A fixed number of turns set in the system prompt\nB) An explicit, checkable completion condition (e.g., tool result matches success criteria)\nC) The model\u0026rsquo;s own natural-language judgment that it is \u0026ldquo;done\u0026rdquo;\nD) A hard timeout only\nAnswer: B — relying purely on the model saying \u0026ldquo;I\u0026rsquo;m done\u0026rdquo; (natural-language loop termination) is a known anti-pattern; termination should be verifiable.\nQ5. An agent keeps calling a search tool even after finding the answer, because there\u0026rsquo;s no verification step. What\u0026rsquo;s missing from the loop?\nA) A retry policy\nB) A verify/check stage that confirms the goal state before exiting\nC) A system prompt\nD) Prompt caching\nAnswer: B.\nQ6. Which is the best example of \u0026ldquo;verify results\u0026rdquo; in an agentic loop for a code-fixing agent?\nA) The agent states it believes the bug is fixed\nB) The agent re-runs the test suite and checks for a pass/fail signal\nC) The agent asks the user if it looks right\nD) The agent moves to the next task after one edit\nAnswer: B — verification should be a deterministic, checkable action, not a self-report.\nQ7. True/False: Increasing max iterations in an agentic loop is a substitute for proper stop conditions.\nAnswer: False — a high iteration cap without real completion/failure conditions leads to runaway loops and wasted cost; it doesn\u0026rsquo;t fix a missing termination signal.\nSection C: Task Decomposition \u0026amp; Sequential vs Parallel Execution #Task Decomposition: #Breaking one big, messy task into smaller pieces that can each be done and checked on their own. Good decomposition splits along natural, independent chunks of work — not just \u0026ldquo;split the words in half.\u0026rdquo;\nSequential Execution #Steps run one after another, because each step needs the result of the one before it (e.g., you can\u0026rsquo;t load data before you\u0026rsquo;ve validated it).\nParallel Execution #Steps run at the same time, because they don\u0026rsquo;t depend on each other (e.g., fetching data from two unrelated APIs). Parallel execution saves time but adds a challenge: someone has to combine the results afterward.\nQ8. A task requires:\n(1) fetch data from API A,\n(2) fetch data from API B,\n(3) merge and summarize both.\nWhich execution structure minimizes latency without sacrificing correctness?\nA) Fully sequential: A → B → merge\nB) A and B run in parallel (independent), then merge runs after both complete\nC) Fully parallel including merge\nD) Random order\nAnswer: B — independent subtasks parallelize; the dependent step (merge) must wait.\nQ9. When decomposing a large task into subtasks for an agent, which is the strongest criterion for splitting?\nA) Split by number of words in the instructions\nB) Split along independent, verifiable units of work with clear success criteria\nC) Always split into exactly 3 subtasks\nD) Never split — one large task is more reliable\nAnswer: B.\nQ10. A data-pipeline agent must\n(1) validate schema,\n(2) transform records,\n(3) load to warehouse — each step depends on the prior succeeding.\nWhat orchestration pattern is this?\nA) Parallel fan-out\nB) Sequential pipeline with dependency gating\nC) Hub-and-spoke\nD) Map-reduce\nAnswer: B.\nQ11. Why might over-decomposing a task into too many tiny subagent calls hurt a system?\nA) It always improves accuracy\nB) It increases coordination overhead, latency, and cost without proportional benefit\nC) Claude cannot handle more than 2 subtasks\nD) It removes the need for verification\nAnswer: B.\nSection D: Multi-Agent Orchestration (Hub-and-Spoke, Coordinator/Subagent) #Multi-Agent Orchestration #Using more than one agent to solve a task, instead of one agent doing everything. Usually one agent manages, others execute.\nHub-and-Spoke Pattern #A specific multi-agent setup:\nHub (coordinator): breaks the task down, hands out pieces to other agents, and combines their answers at the end. Spokes (subagents): each does one focused piece of work and reports back. Why use it: keeps each subagent\u0026rsquo;s job small and focused, so it doesn\u0026rsquo;t get confused doing ten things at once.\nSubagent #A smaller, specialized agent that gets its own separate context window (its own \u0026ldquo;workspace\u0026rdquo;) to do one job. Keeping it separate from the main agent stops irrelevant information from cluttering things up — this is called avoiding context pollution.\nCoordinator #Another name for the \u0026ldquo;hub\u0026rdquo; in hub-and-spoke — the agent in charge of splitting work up, sending it out, and merging results back together. Doesn\u0026rsquo;t usually do the hands-on work itself.\nScoped Tool Access #Giving each agent/subagent only the tools it actually needs for its job, not the entire toolbox. This limits how much damage a mistake can cause (smaller \u0026ldquo;blast radius\u0026rdquo;) and keeps the agent focused.\nQ12. In a hub-and-spoke multi-agent design, what is the role of the hub (coordinator)?\nA) It performs all the actual domain work itself\nB) It decomposes the task, dispatches to specialized subagents, and integrates their results\nC) It only logs errors\nD) It replaces the need for tool use\nAnswer: B.\nQ13. Why use isolated subagents (separate context windows) instead of one large agent doing everything in a single context?\nA) Isolation prevents any tool use\nB) It keeps each subagent\u0026rsquo;s context focused, reduces context pollution, and allows specialization\nC) It\u0026rsquo;s required by the Claude API\nD) It removes the need for a coordinator\nAnswer: B.\nQ14. A coordinator dispatches 5 subagents to research 5 competitors in parallel, then synthesizes a report. What risk does this pattern specifically introduce that a sequential single-agent approach wouldn\u0026rsquo;t?\nA) Increased latency\nB) Inconsistent formatting/conflicting findings across subagents that the coordinator must reconcile\nC) Inability to use tools\nD) Loss of the system prompt\nAnswer: B — parallel independent subagents can return inconsistent or conflicting outputs; the coordinator\u0026rsquo;s integration step must handle this.\nQ15. Which scenario is the WEAKEST fit for multi-agent orchestration?\nA) Researching 10 independent topics in parallel\nB) A simple two-step task: fetch one value, format it into a sentence\nC) Reviewing a large codebase where each subagent audits a different module\nD) Running independent evaluations across multiple test scenarios\nAnswer: B — trivial, tightly coupled two-step tasks don\u0026rsquo;t need orchestration overhead; a single call suffices.\nQ16. What is a key architectural reason to give subagents restricted/scoped tool access rather than the full toolset?\nA) It\u0026rsquo;s required by the API\nB) It limits blast radius and keeps each subagent\u0026rsquo;s decision space aligned to its specific job\nC) It speeds up token generation\nD) It removes the need for permission checks\nAnswer: B.\nSection E: Hooks vs Prompts #Hooks #Hard, code-level rules that run outside of Claude\u0026rsquo;s own reasoning and can block, modify, or intercept an action before it happens — no matter what Claude \u0026ldquo;decides.\u0026rdquo; Used when something absolutely must never happen (e.g., never delete production data).\nKey idea: Hooks are guaranteed. They don\u0026rsquo;t rely on Claude following instructions correctly.\nPrompts (as a control mechanism) #Instructions written in plain language that guide Claude\u0026rsquo;s behavior by influence, not force. Good for soft preferences (tone, style) — but Claude could still deviate, especially under confusing input. Not safe for hard rules.\nKey contrast: Hooks = guaranteed / deterministic. Prompts = likely / probabilistic.\nQ17. You need to GUARANTEE that no agent ever writes to a production database, even if the model misunderstands instructions. What\u0026rsquo;s the right mechanism?\nA) Add a strong instruction in the system prompt\nB) A deterministic hook/guardrail that blocks or intercepts the tool call before execution\nC) Ask the model to double-check itself\nD) Increase temperature to 0\nAnswer: B — prompts are persuasive, not enforced; hooks (or equivalent deterministic checks) are needed for hard guarantees.\nQ18. When should you prefer a prompt-based instruction over a hook for controlling agent behavior?\nA) When the behavior must be 100% deterministic and safety-critical\nB) When the desired behavior is a matter of style, tone, or soft preference, not a hard constraint\nC) Never — hooks are always better\nD) When there is no tool use involved\nAnswer: B.\nQ19. True/False: Hooks execute deterministically outside the model\u0026rsquo;s reasoning, while prompts only influence the model\u0026rsquo;s probabilistic behavior.\nAnswer: True.\nSection F: Session Resumption \u0026amp; State #Session Resumption #The ability to pick a long task back up where it left off (e.g., after a crash or interruption) instead of starting over from scratch.\nCheckpointing #Saving progress at intervals during a long task, so that if something breaks, the system can resume from the last saved point instead of redoing already-finished (and already-verified) work.\nstop_reason (the real termination signal) #Every Claude API response includes a stop_reason field — this is the correct, reliable way to know whether Claude wants to keep going (e.g. tool_use) or has actually finished (end_turn). The exam treats checking stop_reason as the right answer, and parsing Claude\u0026rsquo;s text for phrases like \u0026ldquo;I\u0026rsquo;m done\u0026rdquo; as the wrong one. This is the concrete, code-level version of \u0026ldquo;natural-language loop termination\u0026rdquo; below — good to know both the plain-English idea and this exact API term.\nContext Sharing (for subagents) #A design decision about how much of the main agent\u0026rsquo;s context a subagent gets to see when it\u0026rsquo;s spawned — full history, a summary, or just the specific inputs it needs. Too much shared context defeats the purpose of isolation; too little and the subagent lacks what it needs to do its job.\nSpawning Strategy #The rule for when and how many subagents get created — e.g., spawn one per independent subtask up front (parallel fan-out) vs. spawn subagents dynamically as the coordinator discovers new work.\nForking (workflows) #Splitting an in-progress session into multiple independent branches that can each continue separately (e.g., to try two different approaches from the same checkpoint). Different from resumption — resumption continues one path after a stop; forking creates multiple paths from one point.\nWorkflow Handoffs #The hand-off point where one agent/session passes control (and relevant state) to another — e.g., a triage agent handing a ticket to a specialist agent. Design questions here: what state transfers, what gets dropped, and how the receiving agent knows what already happened.\nOver-Engineering (the #1 way this domain loses marks) #Reaching for a complex pattern (multi-agent orchestration, elaborate loops) when a simpler workflow or single agent call would solve the task just as well. The exam specifically tests whether you can recognize when the simple answer is correct — not just whether you know the advanced patterns exist.\nQ20. A long-running agentic task gets interrupted mid-way (process crash). What architectural feature allows it to continue without redoing completed work?\nA) A higher max_tokens setting\nB) Persisted session/task state that can be resumed from the last checkpoint\nC) A larger context window\nD) Prompt caching alone\nAnswer: B.\nQ21. Why is checkpointing progress important in long multi-step agentic workflows?\nA) It reduces the model\u0026rsquo;s temperature\nB) It allows recovery and resumption without re-executing already-verified steps, saving cost and avoiding duplicate side effects\nC) It\u0026rsquo;s only relevant for parallel agents\nD) It replaces the need for verification\nAnswer: B.\nSection G: Anti-Patterns (High-Yield) #Natural-Language Loop Termination #Ending an agentic loop just because Claude says \u0026ldquo;I\u0026rsquo;m done\u0026rdquo; in plain text, instead of checking a real, verifiable condition (like a test passing or a value matching). Risky because Claude can be wrong about its own completion.\nSame-Session Self-Review #Having the same agent, in the same context, both do the work AND grade whether the work is correct. Risky because it\u0026rsquo;s checking its own homework — it shares the same blind spots and assumptions that led to the original output.\nBetter approach: verify with a separate process, a fresh context, or an independent check.\nTool Sprawl #Giving an agent too many tools, especially ones that overlap or sound similar. This makes it harder for Claude to pick the right tool, wastes context space listing all the tool descriptions, and increases mistakes.\nQ22. \u0026ldquo;Natural-language loop termination\u0026rdquo; refers to which anti-pattern?\nA) Ending a loop based on the model saying it\u0026rsquo;s finished, rather than a checkable condition\nB) Using JSON instead of prose\nC) Terminating a loop after exactly one tool call\nD) Using hooks to stop a loop\nAnswer: A.\nQ23. \u0026ldquo;Same-session self-review\u0026rdquo; as an anti-pattern means:\nA) The agent reviews its own output for tone\nB) The same agent instance/context that produced an output also grades/verifies its own correctness, introducing bias and blind spots\nC) Two subagents reviewing each other\nD) A human reviewing agent output\nAnswer: B — verification is more reliable when done by an independent process/context (or fresh session) than by the same context that generated the work.\nQ24. \u0026ldquo;Tool sprawl\u0026rdquo; describes:\nA) Giving an agent too many tools with overlapping or unclear purposes, degrading its tool-selection accuracy\nB) Using too few tools\nC) A tool that calls other tools\nD) Parallel tool execution\nAnswer: A.\nQ25. A team gives their agent 40 tools \u0026ldquo;just in case,\u0026rdquo; many with similar names and overlapping functionality. What\u0026rsquo;s the most likely consequence?\nA) Improved accuracy from more options\nB) Increased tool-selection errors and wasted context on tool schemas\nC) Lower latency\nD) No effect\nAnswer: B.\nSection H: Scenario-Style (Exam-Realistic) #Q26. You\u0026rsquo;re designing an agent to triage incoming support tickets, and escalate to a human for billing disputes over $500 but auto-resolve everything else. Which combination of concepts from this domain applies MOST directly?\nA) Task decomposition only\nB) A workflow/agent hybrid with a deterministic guardrail (hook) enforcing the escalation threshold, not just a prompt instruction\nC) Pure conversational pattern\nD) Hub-and-spoke with 10 subagents\nAnswer: B — the $500 threshold is a hard business rule; it should be enforced deterministically (hook/code check), not left to the model\u0026rsquo;s prompt-following.\nQ27. An agent\u0026rsquo;s loop keeps re-fetching the same API data 15 times because its \u0026ldquo;am I done?\u0026rdquo; check is a vague self-assessment prompt. Identify the two problems present.\nA) Tool sprawl and hub-and-spoke misuse\nB) Natural-language loop termination and missing verification step\nC) Session resumption failure and hook misuse\nD) Sequential vs parallel confusion\nAnswer: B.\nQ28. A codebase-review system uses one coordinator to assign each subagent a different module, run them in parallel, then merge findings into one report, flagging conflicting recommendations for human review. Which best describes the design quality?\nA) Poor — should be sequential\nB) Good — matches hub-and-spoke pattern with parallel independent work and appropriate conflict handling at integration\nC) Poor — subagents should share one context\nD) Poor — no tools should be used\nAnswer: B.\nQuick Contrast Table # Term Term Key Difference Agent Workflow Agent decides its own steps; workflow has fixed steps Sequential Parallel Sequential = dependent steps; Parallel = independent steps, needs merging Hooks Prompts Hooks = enforced by code; Prompts = suggested to the model Hub Spoke Hub coordinates/merges; Spoke executes one focused job Verification Self-report Verification = checkable proof; Self-report = Claude\u0026rsquo;s own claim (\u0026ldquo;I\u0026rsquo;m done\u0026rdquo;) Quick Reference: Key Terms to Know Cold # Agentic loop: gather context → take action → verify → repeat/terminate Workflow vs agent: fixed predictable steps vs model-driven dynamic steps Hub-and-spoke: coordinator decomposes + dispatches + integrates; subagents execute scoped work Hooks vs prompts: hooks = deterministic enforcement; prompts = probabilistic guidance Session resumption: checkpointed state enabling recovery without redoing work Anti-patterns: natural-language loop termination, same-session self-review, tool sprawl Study tip: for a 27%-weighted domain, expect scenario questions (\u0026ldquo;what\u0026rsquo;s wrong with this design?\u0026rdquo;) more than definition recall. Practice spotting anti-patterns in a described system.\nIn next post we will look for Domain 2: Tool design and MCP Integration.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/cca-f-domain-1-agentic-architecture-orchestration-key-concepts/","section":"Posts","summary":"\u003ch3 id=\"i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" class=\"relative group\"\u003eI am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eWhere to Register: \u003ca href=\"https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"topics-covered\" class=\"relative group\"\u003eTopics Covered \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#topics-covered\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cul\u003e\n\u003cli\u003eagentic vs workflow vs conversational systems,\u003c/li\u003e\n\u003cli\u003ethe agentic loop (gather context → act → verify),\u003c/li\u003e\n\u003cli\u003etask decomposition,\u003c/li\u003e\n\u003cli\u003esequential vs parallel execution,\u003c/li\u003e\n\u003cli\u003emulti-agent orchestration (hub-and-spoke, coordinator/subagent patterns),\u003c/li\u003e\n\u003cli\u003ehooks vs prompts,\u003c/li\u003e\n\u003cli\u003esession resumption, and\u003c/li\u003e\n\u003cli\u003ecommon anti-patterns.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003eUnofficial practice set, written to match publicly reported blueprint topics — not sourced from the real exam bank. Use for concept drilling, not as a guarantee of exam content.\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e","title":"CCA-F Domain1: Agentic Architecture \u0026 Orchestration - Key Concepts"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/ccaf/","section":"Categories","summary":"","title":"CCAF"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/ccaf/","section":"Tags","summary":"","title":"CCAF"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/claude/","section":"Categories","summary":"","title":"Claude"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/claude/","section":"Tags","summary":"","title":"Claude"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/genai/","section":"Tags","summary":"","title":"GenAI"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/localmodel/","section":"Tags","summary":"","title":"LocalModel"},{"content":"Everyone has used log4j libraries for logging in their application or for learning. It is also talk of the town after CVE Remote Code Execution vulnerability added recently.\nRemote Code Execution (RCE) #It is one of the most dangerous kind of vulnerabilities, where an attacker will be able to execute any malicious code without your knowledge. This is how kaspersky enclyopedia defines RCE as\nOne of the most dangerous types of computer vulnerabilities. It allows an attacker to remotely run malicious code within the target system on the local network or over the Internet. Physical access to the device is not required. An RCE vulnerability can lead to loss of control over the system or its individual components, as well as theft of sensitive data.\nResolution/Mitigation Steps #In order to resolve this one and for all, we have to use the latest version of log4j, which is 2.15.0. This is the maven dependency to use:\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.logging.log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.15.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; UPDATE: Use log4j latest build which is 2.16.0, where they have fixed some edge cases missed in 2.15.0 #But let\u0026rsquo;s say you cannot edit the code, you can also mitigate by\nFor release \u0026gt;=2.10, adding log4j2.formatMsgNoLookups to true as system parameter while running you application like below will prevent from this vulnerability.\njava -Dlog4j2.formatMsgNoLookups=true abc..\nFor releases from 2.0_beta9 to 2.10.0, remove org/apache/logging/log4j/core/lookup/JndiLookup.class from the classpath.\nFor Java version greater than 8u121, it can be protected against RCE by defaulting the com.sun.jndi.rmi.object.trustURLCodebase and com.sun.jndi.cosnaming.object.trustURLCodebase to false.\nSetting the environment variable *LOG4J_FORMAT_MSG_NO_LOOKUPS *to true.\nThe first and the last point looks the easiest for me.\nIts a high chance that your application or the third party libraries your application is using, is in-turn using log4j and that\u0026rsquo;s why it is currently the hottest topic in market right now. Use the above steps to fix this vulnerability as soon as possible.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/log4jshell-vulnerability/","section":"Posts","summary":"\u003cp\u003eEveryone has used log4j libraries for logging in their application or for learning. It is also talk of the town after \u003ca href=\"https://nvd.nist.gov/vuln/detail/CVE-2021-44228\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eCVE Remote Code Execution vulnerability\u003c/em\u003e\u003c/a\u003e added recently.\u003c/p\u003e\n\u003ch3 id=\"remote-code-execution-rce\" class=\"relative group\"\u003e\u003cstrong\u003eRemote Code Execution (RCE)\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#remote-code-execution-rce\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eIt is one of the most dangerous kind of vulnerabilities, where an attacker will be able to execute any malicious code without your knowledge. This is how kaspersky enclyopedia defines RCE as\u003c/p\u003e","title":"Log4JShell Vulnerability"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/ollama/","section":"Categories","summary":"","title":"Ollama"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/ollama/","section":"Tags","summary":"","title":"Ollama"},{"content":"Have you ever excitedly run ollama run llama3:70b on your 16GB MacBook Air, only to watch your system grind to an absolute halt? The fan spins up like a jet engine, your cursor freezes, and your swap memory immediately maxes out.\nThe biggest bottleneck in local AI isn\u0026rsquo;t the models themselves—it\u0026rsquo;s hardware-model mismatch.\nEngineers are blindly downloading quantized models without calculating the overhead of the OS, background applications, model weights, and the often-ignored KV (Key-Value) cache. When a model spills from VRAM/Unified Memory into CPU system RAM (or worse, disk swap), inference speed drops from a usable 30 tokens/second to a painful 1 token/second.\nWe got tired of guessing. To solve this, we built OllamaAdvisor: an intelligent, zero-backend React web application that profiles your hardware, mathematically predicts Out-of-Memory (OOM) errors before you download a model, and dynamically generates the exact configurations required to run it smoothly.\nHere’s a deep dive into how we engineered it.\nOllama Advisor Walkthrough\nArchitecture \u0026amp; Repo Map #OllamaAdvisor is built as a pure client-side Single Page Application (SPA). Because we are just crunching hardware numbers against a static model registry, there was no need to introduce the latency, complexity, or cost of a Node.js backend.\nCore Stack:\nReact 18 \u0026amp; TypeScript: For strict type safety on hardware profiles and predictable UI state manipulation. Vite: Chosen for its lightning-fast HMR and highly optimized production builds (npm run build). Tailwind CSS: For building a premium, dynamic, and responsive UI without fighting massive stylesheet bundles. If you look under the hood, the structural data-flow is extremely focused:\n📦 ollama-advisor\n┣ 📂 src\n┃ ┣ 📂 components\n┃ ┃ ┣ 📜 HardwareForm.tsx # Captures user hardware constraints \u0026amp; use case\n┃ ┃ ┣ 📜 MemoryBar.tsx # Real-time visualizer for RAM pressure\n┃ ┃ ┣ 📜 ModelCard.tsx # Renders the final recommendation \u0026amp; configs\n┃ ┃ ┗ 📜 CodeBlock.tsx # Displays copyable Modelfile/Env configs\n┃ ┣ 📂 data\n┃ ┃ ┗ 📜 models.ts # Static registry of LLMs, sizes, and default contexts\n┃ ┣ 📂 utils\n┃ ┃ ┗ 📜 advisor.ts # THE BRAIN: Hardware profiling \u0026amp; scoring engine\n┃ ┣ 📜 App.tsx # Main orchestrator linking state to UI\n┃ ┣ 📜 index.css # Tailwind tokens and base styles\n┃ ┗ 📜 main.tsx # Application bootstrap\n┣ 📜 package.json # Dependencies (lucide-react, tailwindcss, vite)\n┗ 📜 docker-compose.yml # Containerized dev/prod deployment\nData enters through the HardwareForm, gets funneled directly into our pure-function utility getRecommendations(), and exits via an array of ranked ModelCards.\nCore Features \u0026amp; Technical Capabilities # Predictive Memory Splitting: It doesn\u0026rsquo;t just look at total RAM. It subtracts OS overhead (e.g., hardcoded 1.5GB for macOS, 0.8GB for Linux, 2.2GB for Windows) and active application usage to find the True Available Memory. Dynamic Context Window Scaling: Large context windows eat massive amounts of memory via the KV cache. If a model fits but the KV cache doesn\u0026rsquo;t, the engine automatically steps down the recommended context (e.g., from 32k to 8k or 4k) to prevent OOM crashes. Intelligent Scoring Matrix: Recommendations aren\u0026rsquo;t just based on memory. Models are scored based on the user\u0026rsquo;s declared use-case (e.g., +18 points if the user wants coding and the model has a coding tag) and speed priority (sacrificing quality for fast generation). Auto-Generated Fine-Tuning Artifacts: It programmatically generates customized Modelfile parameters (like num_thread calculated based on CPU cores) and critical environment variables (like OLLAMA_FLASH_ATTENTION=1 and OLLAMA_KV_CACHE_TYPE=q8_0 for low-RAM machines).Ollama Advisor Explanation Code Snippet Deep-Dive: The Recommendation Engine #The magic happens in src/utils/advisor.ts. Let\u0026rsquo;s look at a critical section of the getRecommendations loop where we determine if a model actually fits and how we penalize it if the memory budget is too tight.\ntypescript // Effective pool limits based on unified memory vs discrete VRAM const effectivePool = hw.gpuType === \u0026#39;apple-silicon\u0026#39; ? totalAvailable : hw.gpuType === \u0026#39;none\u0026#39; ? totalAvailable : Math.min(totalAvailable, hw.gpuVRAMGB); // Inside the model evaluation loop: const remainingAfterModel = effectivePool - model.sizeGB; // Hard skip: can\u0026#39;t even hold model weights if (remainingAfterModel \u0026lt; -1) continue; const fits = remainingAfterModel \u0026gt;= model.kvCacheGB8k; const tight = !fits || (model.sizeGB / effectivePool) \u0026gt; 0.72; // Dynamically scale context window based on remaining budget const recommendedCtx = fits ? recommendedCtxForBudget(model, remainingAfterModel) : 4096; const kvCacheActual = model.kvCacheGB8k * (recommendedCtx / 8192); const freeBuffer = hw.totalRAMGB - osOverhead - hw.activeAppsGB - model.sizeGB - kvCacheActual; // ── Scoring ────────────────────────────────────────────────────────── let score = model.qualityScore * 10 Dynamic ROI / Resource Calculator #Wondering how much memory you\u0026rsquo;re actually wasting?\nUse this mental model when setting up your next local LLM:\nThe Ollama Memory Formula: Total RAM - (OS Overhead + App Usage) = Your True AI Budget\nExample Scenario:\nSystem: 16GB M4 Mac OS + Chrome + IDE: ~5.5GB True AI Budget: ~10.5GB Actionable Advice: Skip 13B/14B models. Stick to highly quantized 7B or 8B models (like Llama 3 8B) at an 8k context window to maintain \u0026gt;20 tokens/second without thermal throttling. By mathematically verifying your hardware constraints beforehand with OllamaAdvisor, you stop thrashing your swap disk and get back to actually building AI features.\nYou can refer to the codebase on my github account\nGithub: https://github.com/ankitagrahari/OllamAdvisor\nIf you like the post, kindly give a like, comment and share.\nFollow @backendbrilliance on instagram.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/stop-guessing-find-perfect-local-llm-with-ollamaadvisor/","section":"Posts","summary":"\u003cp\u003eHave you ever excitedly run \u003cem\u003eollama run llama3:70b\u003c/em\u003e on your 16GB MacBook Air, only to watch your system grind to an absolute halt? The fan spins up like a jet engine, your cursor freezes, and your swap memory immediately maxes out.\u003c/p\u003e\n\u003cp\u003eThe biggest bottleneck in local AI isn\u0026rsquo;t the models themselves—it\u0026rsquo;s \u003cstrong\u003ehardware-model mismatch\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eEngineers are blindly downloading quantized models without calculating the overhead of the OS, background applications, model weights, and the often-ignored KV (Key-Value) cache. When a model spills from VRAM/Unified Memory into CPU system RAM (or worse, disk swap), inference speed drops from a usable 30 tokens/second to a painful 1 token/second.\u003c/p\u003e","title":"Stop Guessing, Find Perfect Local LLM with OllamaAdvisor"},{"content":"AI Tutor is a web application that uses Spring Boot and Spring AI on the backend, an Ollama-hosted LLM (e.g. Google’s Gemma3) for natural language understanding, and Vaadin for the rich web UI. Its core innovation is a Retrieval-Augmented Generation (RAG) pipeline: when the user uploads course materials (PDFs, text, etc.), the app splits them into chunks, creates vector embeddings, and stores these in a PGVector-enabled PostgreSQL database. At chat time, similar chunks are retrieved and included as context so the LLM can answer questions specific to the uploaded documents, avoiding “hallucinations” .\nTech Stack and Configuration #The project uses Spring AI to glue all pieces together. In application.yml, we configure Ollama and PGVector. For example:\nspring: datasource: url: jdbc:postgresql://localhost:5432/vectordb username: aagrahari password: password ai: ollama: base-url: http://localhost:11434 chat: options: model: gemma3:4b temperature: 0.7 embedding: options: model: mxbai-embed-large vectorstore: pgvector: initialize-schema: true schema-name: public table-name: vector_store schema-validation: true index-type: HNSW distance-type: COSINE_DISTANCE dimensions: 1536 max-document-batch-size: 10000 # Optional: Maximum number of documents per batch Here we tell Spring AI to connect to the local Ollama server (base-url), use the gemma3:4b model for chat, and use the mxbai-embed-large model for embeddings. We also enable PGVector and set the index to HNSW with cosine distance, matching the 1024-dimensional embedding vectors. When the app starts, Spring AI will initialize the database schema (via initialize-schema=true) and (if needed) pull the specified models locally so that responses are generated by the on-prem Ollama models.\nDocument Ingestion and Embeddings #When the user uploads a file, the backend reads and chunks it, then creates and stores embeddings. In the Vaadin MainView, we set up an Upload component and handle successful uploads.\n// In MainView class: private static @NonNull Upload getUpload(FileUploadHandler fileUploadHandler) { Upload docUpload = new Upload(fileUploadHandler); docUpload.setAcceptedFileTypes(\u0026#34;application/pdf\u0026#34;, \u0026#34;.pdf\u0026#34;, \u0026#34;.doc\u0026#34;); docUpload.setMaxFiles(1); int maxFileSizeInBytes = 10 * 1024 * 1024; // 10MB docUpload.setMaxFileSize(maxFileSizeInBytes); // docUpload.setAutoUpload(false); docUpload.addFileRejectedListener(event -\u0026gt; { String errorMsg = event.getErrorMessage(); Notification notification = Notification.show(errorMsg, 5000, Notification.Position.MIDDLE); notification.addThemeVariants(NotificationVariant.LUMO_ERROR); }); return docUpload; } When a file is uploaded, we parse it and split it into chunks using Apache Tika and a text splitter. In the listener:\npublic void uploadToVectorDB(String directoryName){ //Read from directory all the files //Split the documents and upload to VectorStore log.info(\u0026#34;RAG Service processing files under directory {}\u0026#34;, directoryName); PdfDocumentReaderConfig readerConfig = PdfDocumentReaderConfig .builder() .withPageExtractedTextFormatter( new ExtractedTextFormatter.Builder() .withNumberOfBottomTextLinesToDelete(0) .withNumberOfTopPagesToSkipBeforeDelete(0) .build()) .withPagesPerDocument(1) .build(); List\u0026lt;Resource\u0026gt; resources = getFilesUnderDirectory( directoryName); resources.forEach(resource -\u0026gt; { PagePdfDocumentReader pdfDocumentReader = new PagePdfDocumentReader(resource, readerConfig); var textSplitter = new TokenTextSplitter(); vectorStore.accept( textSplitter.apply( pdfDocumentReader.get())); }); log.info(\u0026#34;Document uploaded to vector store!!\u0026#34;); } Each Document chunk carries a metadata field source=filename. Calling vectorStore.accept(docs) instructs Spring AI to invoke the configured embedding model (mxbai-embed-large) on each text chunk and store the resulting vectors in PostgreSQL. The uploadedFiles multi-select box is updated so the user can choose which documents to include in the chat context.\nChat Service (Spring AI Integration) #The core chat logic is encapsulated in a ChatService Spring component, which is injected into the UI. In this service we build a ChatClient using Spring AI’s fluent builder. We set a system prompt to instruct the model (e.g. to be concise and knowledgeable), add an in-memory ChatMemory for conversation history, and attach a QuestionAnswerAdvisor (for RAG) and a simple logger.\npublic ChatService(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory, VectorStore vectorStore) { this.vectorStore = vectorStore; // Add a memory advisor to the chat client var chatMemoryAdvisor = MessageChatMemoryAdvisor .builder(chatMemory) .build(); // Build the chat client chatClient = chatClientBuilder .defaultAdvisors(chatMemoryAdvisor) .build(); } The prompt template looks something likes this:\nYou are a helpful and friendly AI assistant who can answer questions based on the document uploaded strictly in DOCUMENTS section. Anything outside the document uploaded should sent response \u0026#34;This questions is not related to the document uploaded!\u0026#34;. Use the DOCUMENTS section to answer accurately. If unsure or if the answer isn\u0026#39;t found in the DOCUMENTS section, simply reply \u0026#34;Information Not available for me respond!\u0026#34; QUESTIONS: {input} DOCUMENTS: {documents} This constructor defines the RAG logic. The QuestionAnswerAdvisor uses the VectorStore to find the most relevant document chunks (based on the user’s question and selected sources). The MessageChatMemoryAdvisor ensures conversation history is preserved across prompts. We do not explicitly specify the model here; Spring AI uses the Ollama configuration we set in application.yml to choose gemma3:4b as the chat model.\nTo ask a question, the service provides a method that sends a prompt to the model with the proper advisors and metadata:\npublic Flux\u0026lt;String\u0026gt; chatStream(String message, String chatId) { PromptTemplate template = new PromptTemplate(sbPromptTemplate); Map\u0026lt;String, Object\u0026gt; promptParam = new HashMap\u0026lt;\u0026gt;(); promptParam.put(\u0026#34;input\u0026#34;, message); promptParam.put(\u0026#34;documents\u0026#34;, String.join(\u0026#34;\\n\u0026#34;, findSimilarDocuments(message))); return chatClient.prompt(template.create(promptParam)) .advisors(advisorSpec -\u0026gt; advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId) ) .stream() .content(); } private List\u0026lt;String\u0026gt; findSimilarDocuments(String message) { List\u0026lt;Document\u0026gt; similarDocuments = vectorStore.similaritySearch( SearchRequest.builder().query(message).topK(3).build() ); return similarDocuments.stream() .map(Document::getText).toList(); } Here we build a chat request with the user’s question, pass in the conversation ID (so the in-memory history is keyed to the Vaadin UI session), and include a filter expression so that the QuestionAnswerAdvisor only searches embeddings tagged with the selected source files. The returned ChatResponse contains the LLM’s answer .\nVaadin UI and Chat View #On the front end, a Vaadin MainView assembles the upload component, file selector, and chat interface. We create a text field and button for user input, and a vertical layout to display messages. For example:\n//Analyze the uploaded document Button analyzeButton = new Button(\u0026#34;Analyze Uploaded Documents\u0026#34;); analyzeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); analyzeButton.setEnabled(triple.getLeft()); analyzeButton.addClickListener(event -\u0026gt; { //Call the RAGService to upload the document to Vector Database. ragService.uploadToVectorDB(triple.getMiddle().getName()); }); docUpload.addAllFinishedListener(event -\u0026gt; { analyzeButton.setEnabled(true); }); buttonLayout.add(analyzeButton); mainFeatures.add( docUpload, buttonLayout ); Span span = new Span(\u0026#34;Chatbot\u0026#34;); Scroller scroller = new Scroller(messageList); scroller.setHeightFull(); var messageInput = new MessageInput(); messageInput.addSubmitListener(this::onSubmit); messageInput.setWidthFull(); getContent().add( header, mainFeatures, span, scroller, messageInput ); The handleNewMessageRequest lambda reads the user’s input, displays it in the UI, calls the ChatService.ask(\u0026hellip;) method, and then appends the assistant’s answer to the chat layout:\nprivate void onSubmit(MessageInput.SubmitEvent submitEvent) { //create and handle a prompt message var promptMessage = new MessageListItem(submitEvent.getValue(), Instant.now(), \u0026#34;User\u0026#34;); promptMessage.setUserColorIndex(0); messageList.addItem(promptMessage); //create and handle the response message var responseMessage = new MessageListItem(\u0026#34;\u0026#34;, Instant.now(), \u0026#34;Bot\u0026#34;); responseMessage.setUserColorIndex(1); messageList.addItem(responseMessage); //append a response message to the existing UI var userPrompt = submitEvent.getValue(); var uiOptional = submitEvent.getSource().getUI(); uiOptional.ifPresent(ui -\u0026gt; chatService.chatStream(userPrompt, chatId) .subscribe(token -\u0026gt; ui.access(() -\u0026gt; responseMessage.appendText(token)))); } This code uses Spring AI’s ChatService to fetch an answer. The session ID ensures each browser session has its own chat history. After sending the message, the Vaadin view scrolls and shows the assistant’s response in the conversation.\nIn summary, the MainView wires together:\nAn Upload component (for PDF) to upload documents. A message display area (chatContainer) and input controls. The ChatService, which runs the RAG-enabled Spring AI logic. Putting It All Together #With these pieces, the AI Tutor works as follows:\nDocument Upload: User uploads course materials. The backend chunks and embeds these, storing vectors in PGVector.\nQuestion Input: User asks a question in the chat UI.\nRetrieval: Spring AI’s QuestionAnswerAdvisor queries PGVector for chunks similar to the question (limited to chosen source files).\nResponse: The LLM (Gemma3 via Ollama) receives the question plus retrieved context, and generates a tailored answer.\nDisplay: The Vaadin UI shows the answer to the user.\nYou can refer to the complete codebase in my Github account @ankitagrahari.\nIf you like the content, do like and comment your feedback. It keeps the motivation machine running.\nReferences: # https://github.com/danvega/spring-ai-workshop/ https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html YT- https://www.youtube.com/playlist?list=PLZV0a2jwt22uoDm3LNDFvN6i2cAVU_HTH https://notebooklm.google.com/ Happy learning :)\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/building-an-ai-tutor-with-spring-ai-ollama-and-vaadin/","section":"Posts","summary":"\u003cp\u003e\u003cstrong\u003eAI Tutor\u003c/strong\u003e is a web application that uses \u003cstrong\u003eSpring Boot\u003c/strong\u003e and \u003cstrong\u003eSpring AI\u003c/strong\u003e on the backend, an \u003cstrong\u003eOllama\u003c/strong\u003e-hosted LLM (e.g. Google’s Gemma3) for natural language understanding, and \u003cstrong\u003eVaadin\u003c/strong\u003e for the rich web UI.  Its core innovation is a Retrieval-Augmented Generation (RAG) pipeline: when the user uploads course materials (PDFs, text, etc.), the app splits them into chunks, creates vector embeddings, and stores these in a \u003cstrong\u003ePGVector\u003c/strong\u003e-enabled PostgreSQL database.  At chat time, similar chunks are retrieved and included as context so the LLM can answer questions \u003cem\u003especific to the uploaded documents\u003c/em\u003e, avoiding “hallucinations”  .\u003c/p\u003e","title":"Building an AI Tutor with Spring AI, Ollama, and Vaadin"},{"content":"As we discussed in the previous post on Domain 4: Claude Code Configuration and Workflow, this part of the post will cover the Domain 5 which holds 15% weightage in the exam. This is also the last domain in this CCFA series. #If you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. #Where to Register: https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\nUnofficial practice set, written to match publicly reported blueprint topics — not sourced from the real exam bank. Use for concept drilling, not as a guarantee of exam content.\nTopics Covered #Prompt Caching \u0026amp; cache_control Breakpoints\nLost-in-the-Middle Effects\nProgressive Summarization Risks\nConversation Compaction\nVerbose Tool Output Trimming\nToken Estimation \u0026amp; Budget Management\nMulti-Turn Conversation Design\nEscalation Decision-Making (explicit criteria + few-shot)\nStructured Error Propagation Across Multi-Agent Systems\nContext Management in Large Codebase Exploration (scratchpad files, subagent delegation)\n1. Prompt Caching \u0026amp; cache_control Breakpoints #Anthropic\u0026rsquo;s API can reuse the already-computed representation (KV cache) of a prompt prefix instead of reprocessing it from scratch on every call — cutting cost and latency. You mark cache breakpoints using cache_control. Critical rule: caching works on prefixes — the cached portion must be identical and must come before the dynamic parts. Stable content goes first (system prompt, project context), volatile content goes last (current message). Any change to the stable prefix (e.g., injecting today\u0026rsquo;s date at the top) invalidates the cache for everything after it.\nQ1. A system prompt starts every request with \u0026ldquo;Today is {current_date}\u0026hellip;\u0026rdquo; before the rest of the fixed instructions. Cache hit rates are near zero. Why?\nA) cache_control isn\u0026rsquo;t supported for system prompts\nB) The changing date sits at the very start of the prefix, so it invalidates the cache for every token that follows it on every single call\nC) The prompt is too short to cache\nD) Dates cannot be included in prompts\nAnswer: B — the \u0026ldquo;don\u0026rsquo;t put volatile content at the top\u0026rdquo; rule, tested directly.\nQ2. Where should a per-user, per-session variable (like a username injected into context) be placed to preserve cache efficiency?\nA) As late as possible — near the dynamic/current-turn content, not mixed into the stable, cached prefix\nB) At the very beginning of the system prompt\nC) It doesn\u0026rsquo;t matter where it goes\nD) It should never be included at all\nAnswer: A.\nQ3. What does a cache_control breakpoint mark in a request?\nA) A point where the conversation ends\nB) A rate-limit threshold\nC) A point in the prompt up to which content should be cached for reuse in future requests with the same prefix\nD) A required field in every JSON schema\nAnswer: C.\nQ4. A team structures their prompt as:\n[current message] → [session history] → [CLAUDE.md project context] → [core system instructions].\nCache hits are poor. What\u0026rsquo;s wrong with this ordering?\nA) Nothing, order doesn\u0026rsquo;t matter\nB) It\u0026rsquo;s backwards — stable content (core instructions, project context) should come first, with the most volatile content (current message) last, so the maximum stable prefix can be cached\nC) CLAUDE.md cannot be cached\nD) Session history should never be included\nAnswer: B.\n2. Lost-in-the-Middle Effects #LLMs tend to pay less attention to information placed in the middle of a long context, compared to information near the beginning or end.\nPractical implication: critical facts buried in the middle of a huge context (long documents, long conversation histories) are more likely to be missed or under-weighted than facts placed at the start or end.\nQ5. A 50-page contract is passed to Claude with the single most important clause sitting on page 25 (dead center). Extraction quality for that clause is worse than for clauses on page 1 or page 50. What effect explains this?\nA) Token limit exceeded\nB) Lost-in-the-middle — information in the middle of long context tends to be attended to less reliably than content at the start or end\nC) The model wasn\u0026rsquo;t given the whole document\nD) This is a JSON Schema issue\nAnswer: B.\nQ6. What\u0026rsquo;s a practical mitigation for lost-in-the-middle effects when a specific fact absolutely must be captured accurately from a long document?\nA) Nothing can be done, it\u0026rsquo;s an inherent limitation\nB) Always increase temperature\nC) Reposition or explicitly re-surface the critical information near the start or end of the context, or break the document into smaller chunks processed separately\nD) Use the Batch API\nAnswer: C.\n3. Progressive Summarization Risks #As a long conversation grows, older parts are often summarized to save space. Repeating this (summarizing an already-summarized summary) compounds information loss — details get progressively vaguer or dropped entirely across each round, and by several rounds in, specific facts from early in the conversation may be irrecoverably gone.\nQ7. A long-running agent session repeatedly summarizes its own summaries every time context fills up. After many rounds, the agent has lost track of a specific requirement stated at the very beginning. What\u0026rsquo;s the cause?\nA) The model has a bug\nB) This is a lost-in-the-middle effect, unrelated to summarization\nC) The requirement was never actually processed\nD) Progressive summarization compounds information loss each time a summary is re-summarized; specific early details erode over repeated rounds Answer: D.\nQ8. What\u0026rsquo;s a safer alternative to repeatedly re-summarizing an already-summarized history?\nA) Summarize even more aggressively each round\nB) Preserve critical facts/decisions verbatim in a durable store (e.g., a scratchpad file or structured state) instead of relying purely on compounding text summaries\nC) Never summarize under any circumstances\nD) Increase the model\u0026rsquo;s context window instead\nAnswer: B.\n4. Conversation Compaction #A built-in mechanism (e.g., in Claude Code) that condenses accumulated session state into a brief summary once context usage gets too high, reclaiming window space so the session can continue — trading some historical detail for continued operability. Distinct from progressive summarization risk in that it\u0026rsquo;s usually a single compaction pass reacting to actual pressure, not a repeated/compounding pattern — but it still loses detail and can strip specifics if triggered carelessly or repeatedly.\nQ9. A Claude Code session hits high context usage on a long feature-build task. What happens under conversation compaction, and what\u0026rsquo;s the trade-off?\nA) The session simply stops working\nB) Accumulated session state gets summarized down to reclaim space so work can continue, at the cost of losing some fine-grained historical detail\nC) Compaction deletes the current task entirely\nD) Compaction only affects tool outputs, never conversation text\nAnswer: B.\nQ10. Why might an architect want to proactively manage context (e.g., via scratchpad files) rather than relying purely on automatic compaction?\nA) Proactive management preserves specific critical facts intact instead of leaving all information subject to lossy automatic summarization when the window fills up\nB) Automatic compaction is always sufficient and needs no supplementing\nC) Compaction is a manual-only feature\nD) There\u0026rsquo;s no reason to; automatic compaction is strictly better\nAnswer: A.\n5. Verbose Tool Output Trimming #Raw tool outputs (large API responses, full file contents, long logs) can consume large amounts of context for little benefit if returned in full every time. Trimming means stripping unnecessary bulk (mechanical bloat like raw dumps, base64 data, redundant metadata) while preserving the actually useful signal — keeping context lean without losing substance.\nQ11. A tool call returns a 50KB raw API response, but the agent only ever needs 3 specific fields from it. What\u0026rsquo;s the architectural fix?\nA) Pass the full 50KB into context every time, \u0026ldquo;just in case\u0026rdquo;\nB) Trim the tool output to the relevant fields before it enters context, discarding the unnecessary bulk\nC) Switch to the Batch API\nD) Increase the context window instead of trimming\nAnswer: B.\nQ12. Why is verbose tool output trimming particularly important in long agentic sessions with many tool calls?\nA) Untrimmed verbose outputs accumulate turn after turn, consuming context budget fast and increasing the risk of hitting limits or triggering lossy compaction sooner\nB) It has no cumulative effect\nC) It only matters for the first tool call\nD) Trimming reduces the model\u0026rsquo;s accuracy\nAnswer: A.\n6. Token Estimation \u0026amp; Budget Management #Proactively estimating how many tokens a task will consume (system prompt + context + expected output) and planning within a budget, rather than discovering you\u0026rsquo;ve hit limits mid-task. Includes accounting for tool schemas (which consume tokens just by being defined, whether called or not) and reserving headroom for output.\nQ13. A system has 15 tools defined, each with a lengthy schema, but a typical task only ever uses 2 of them. What\u0026rsquo;s a token-budget consideration here?\nA) Tool schemas cost nothing unless called\nB) Only called tools count against budget\nC) All defined tool schemas consume context tokens on every request regardless of whether they\u0026rsquo;re actually called, so unused tools still count against the budget\nD) This has no effect on token usage\nAnswer: C — ties back to Domain 2\u0026rsquo;s \u0026ldquo;tool sprawl\u0026rdquo; from a token-budget angle.\nQ14. Why should token budgeting reserve explicit headroom for the model\u0026rsquo;s output, not just the input context?\nA) Output tokens are free\nB) If input consumes nearly the entire context window, there may be insufficient room left for the response the task actually requires, causing truncation\nC) Output never counts against context limits\nD) This only matters for structured output tasks\nAnswer: B.\n7. Multi-Turn Conversation Design #Designing how a conversation accumulates and is structured over many turns — deciding what persists, what gets pruned, when to summarize vs. preserve verbatim, and how earlier decisions/context stay accessible without bloating every subsequent call.\nQ15. A multi-turn support conversation needs the customer\u0026rsquo;s original complaint (turn 1) to remain accurately known even by turn 40. What design choice best preserves this reliably?\nA) Trust that lost-in-the-middle and summarization won\u0026rsquo;t affect it\nB) Ignore the risk since 40 turns isn\u0026rsquo;t very long\nC) Re-ask the customer to repeat it every 10 turns\nD) Explicitly preserve the original complaint as a persistent, structured fact (e.g., in a fixed context slot or state object) rather than letting it become buried and possibly summarized away deep in conversation history\nAnswer: D.\nQ16. What\u0026rsquo;s a key multi-turn design trade-off between keeping full verbatim history vs. aggressive summarization?\nA) There is no trade-off\nB) Full verbatim history preserves accuracy but grows context/cost over time; aggressive summarization saves space but risks losing specific details (progressive summarization risk) — the design must balance both per what\u0026rsquo;s actually critical to retain\nC) Summarization always improves accuracy\nD) Verbatim history is always infeasible\nAnswer: B.\n8. Escalation Decision-Making (Explicit Criteria + Few-Shot) #Designing when an agent should hand off to a human (or a different system) rather than continue autonomously — built the same way as Domain 4\u0026rsquo;s explicit-criteria and few-shot principles: concrete, checkable escalation thresholds, reinforced with few-shot examples covering ambiguous boundary cases, not vague judgment calls.\nQ17. An agent\u0026rsquo;s escalation rule says \u0026ldquo;escalate if the situation seems complicated.\u0026rdquo; Escalation behavior is wildly inconsistent across sessions. What\u0026rsquo;s the fix, (consistent with Domain 4 principles applied here) ?\nA) Remove escalation entirely\nB) Replace the vague rule with explicit, checkable criteria (e.g., specific conditions/thresholds) and reinforce with few-shot examples of ambiguous cases and the correct escalation decision\nC) Escalate on every single turn to be safe\nD) Increase the model\u0026rsquo;s temperature\nAnswer: B.\nQ18. Why are boundary/ambiguous-case examples more valuable than clear-cut examples when teaching an agent escalation behavior?\nA) They aren\u0026rsquo;t more valuable\nB) Boundary cases are easier to write\nC) Clear-cut cases are already handled correctly by default; ambiguous cases are where inconsistent behavior actually happens, so that\u0026rsquo;s where examples add the most signal\nD) Only clear-cut examples should ever be used\nAnswer: C — same principle as Domain 4\u0026rsquo;s few-shot targeting, applied specifically to escalation design.\n9. Structured Error Propagation Across Multi-Agent Systems #When a subagent fails, the failure needs to be reported upward in a structured, informative way (what failed, why, what was attempted) so the coordinator or calling system can make an informed decision — rather than a raw crash, a silent failure, or an ambiguous \u0026ldquo;something went wrong\u0026rdquo; bubbling up.\nQ19. In a hub-and-spoke system, a subagent silently fails and the coordinator receives no signal at all, proceeding as if the subtask succeeded. What\u0026rsquo;s missing?\nA) A larger context window\nB) Structured error propagation — the subagent\u0026rsquo;s failure needs to surface to the coordinator with enough detail to act on, instead of failing silently\nC) A cache_control breakpoint\nD) Conversation compaction\nAnswer: B.\nQ20. What should a well-designed error propagated from a subagent to a coordinator include?\nA) Nothing — the coordinator should just retry blindly\nB) Only a generic \u0026ldquo;error\u0026rdquo; flag with no detail\nC) What failed, why it failed, and what was attempted — similar to Domain 2\u0026rsquo;s structured tool-error design, but at the agent-to-agent level\nD) The subagent\u0026rsquo;s entire raw context dump\nAnswer: C — mirrors Domain 2\u0026rsquo;s structured tool error handling, now at the multi-agent coordination level.\n10. Context Management in Large Codebase Exploration (Scratchpad Files \u0026amp; Subagent Delegation) #For large codebases, dumping the whole repo into context doesn\u0026rsquo;t scale. Two key patterns:\nScratchpad files — Claude writes findings/notes to a file on disk as it explores, instead of holding everything in the context window; the file becomes durable, offloaded memory. Subagent delegation — hand off exploration of a specific module/directory to a subagent with its own scoped, smaller context, reporting back a summary rather than dumping everything into the main session. Q21. An agent exploring a 500-file repository tries to read every file into a single session\u0026rsquo;s context to \u0026ldquo;understand everything first.\u0026rdquo; What\u0026rsquo;s the architectural problem, and the better pattern?\nA) No problem — more context is always better\nB) This will exhaust context budget fast and hit lost-in-the-middle/compaction issues; better to use scratchpad files to persist findings incrementally and/or delegate module-level exploration to subagents that report back summaries\nC) The fix is to switch to the Batch API\nD) The fix is a longer system prompt\nAnswer: B.\nQ22. Why write exploration findings to a scratchpad file instead of just keeping them in the conversation context?\nA) A scratchpad file persists findings durably and outside the token budget, avoiding the risk that important discoveries get lost to summarization or lost-in-the-middle effects as the session grows\nB) Files are faster to read than context\nC) Scratchpad files replace the need for tools entirely\nD) This has no advantage over keeping everything in context\nAnswer: A.\nQ23. When exploring an unfamiliar large codebase, why delegate to subagents scoped by module rather than one agent reading the whole codebase sequentially?\nA) It\u0026rsquo;s slower and offers no benefit\nB) Each subagent gets a focused, smaller context relevant to just its module, avoiding context pollution in the main session, and can work in parallel, reporting back a condensed summary\nC) Subagents cannot read code\nD) This violates MCP rules\nAnswer: B.\nRealistic Scenario Questions (cross-topic) #Q24. A long-running coding session in Claude Code shows degrading recall of a requirement stated 80 turns ago, coinciding with several rounds of automatic compaction. Separately, cache hit rates have also dropped because the team recently started injecting a live \u0026ldquo;current context window %\u0026rdquo; stat at the very top of the system prompt. Diagnose both issues.\nA) Both issues are actually the same root cause\nB) Issue 1: progressive/compounding summarization loss from repeated compaction eroding early details — mitigate with a scratchpad or persisted state for critical facts. Issue 2: the dynamic stat at the top of the prefix invalidates the cache for everything after it — move volatile content to the end\nC) Both are caused by lost-in-the-middle only\nD) Neither issue is fixable\nAnswer: B — combines compaction/summarization risk with cache_control ordering rules.\nQ25. A multi-agent research system has a coordinator dispatching 6 subagents across a large codebase. One subagent times out. The coordinator has no idea why and just reports \u0026ldquo;task failed\u0026rdquo; to the user. What two Domain 5 principles were violated, and what\u0026rsquo;s the fix for each?\nA) Only one principle was violated\nB) This is purely a token-budget issue\nC) This is unrelated to any Domain 5 concept\nD) Structured error propagation was missing (the subagent\u0026rsquo;s failure detail never reached the coordinator) and the coordinator\u0026rsquo;s own error reporting to the user should also be structured/specific rather than generic — fix both by defining clear error schemas at each hop\nAnswer: D.\nQ26. An agent\u0026rsquo;s escalation logic is vague (\u0026ldquo;escalate when unsure\u0026rdquo;), and separately, its raw tool outputs (full API JSON dumps) are never trimmed, causing it to hit context limits mid-task on complex cases — which are exactly the cases most likely to need escalation. How do these two problems compound each other, and what\u0026rsquo;s the combined fix?\nA) Untrimmed verbose tool output eats the token budget fastest precisely on complex, high-tool-use cases — the same cases the vague escalation rule fails to catch — so context pressure and escalation failures compound on the hardest cases; fix by adding explicit escalation criteria (with boundary-case few-shot examples) AND trimming tool output to preserve budget for exactly those harder cases\nB) They\u0026rsquo;re unrelated; fix independently and in either order\nC) The only fix needed is a bigger context window\nD) Only the escalation rule needs fixing\nAnswer: A — deliberately compounding two topics the way exam scenarios reportedly do.\nQuick References # Concept One-line definition Prompt caching Reuses computed prefix representation to cut cost/latency cache_control breakpoint Marks how much of the prompt prefix should be cached Stable-first ordering Put unchanging content first, volatile content last, to preserve cache hits Lost-in-the-middle Content in the middle of long context gets less reliable attention Progressive summarization risk Re-summarizing summaries compounds information loss over rounds Conversation compaction Built-in mechanism condensing session state to reclaim context space Tool output trimming Stripping unneeded bulk from verbose tool responses before they enter context Token budget management Proactively planning token usage across input, tools, and output headroom Multi-turn conversation design Deciding what persists, prunes, or summarizes across many turns Escalation criteria Explicit, checkable thresholds (not vague judgment) for handing off to a human Structured error propagation Failures reported upward with what/why/attempted detail, not silently or vaguely Scratchpad file Durable, on-disk notes offloading findings outside the context window Subagent delegation (codebase) Scoping exploration of a module to a subagent with focused context Recurring exam pattern in this domain: problems that look like \u0026ldquo;context ran out\u0026rdquo; or \u0026ldquo;agent forgot something\u0026rdquo; usually trace back to one of a small set of root causes — cache-breaking prefix ordering, compounding summarization, untrimmed tool output, or missing structured state. Practice diagnosing which one before jumping to a fix.\nReferring to these questions just gives you an idea how the exam looks like, and can vary. Share if you have any answers or questions not correctly placed or explained.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/cca-f-domain-5-context-management-and-reliability/","section":"Posts","summary":"\u003ch3 id=\"as-we-discussed-in-the-previous-post-on-domain-4-claude-code-configuration-and-workflow-this-part-of-the-post-will-cover-the-domain-5-which-holds-15-weightage-in-the-exam-this-is-also-the-last-domain-in-this-ccfa-series\" class=\"relative group\"\u003eAs we discussed in the previous post on \u003ca href=\"/post/cca-f-domain-4-prompt-engineering-and-structured-output\"\u003eDomain 4: Claude Code Configuration and Workflow\u003c/a\u003e, this part of the post will cover the Domain 5 which holds 15% weightage in the exam. This is also the \u003cem\u003e\u003cstrong\u003elast domain\u003c/strong\u003e\u003c/em\u003e in this CCFA series. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#as-we-discussed-in-the-previous-post-on-domain-4-claude-code-configuration-and-workflow-this-part-of-the-post-will-cover-the-domain-5-which-holds-15-weightage-in-the-exam-this-is-also-the-last-domain-in-this-ccfa-series\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003ch3 id=\"if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" class=\"relative group\"\u003eIf you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eWhere to Register: \u003ca href=\"https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\u003c/a\u003e\u003c/p\u003e","title":"CCA-F Domain 5: Context Management and Reliability"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/619c9d9d8da2c200166cb558/","section":"Categories","summary":"","title":"[\"619c9d9d8da2c200166cb558\"]"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/","section":"Tags","summary":"","title":"[]"},{"content":" Live at: https://hookspy.inHookSpy - Walk Through\nEvery developer integrating Stripe, Razorpay, or GitHub has been there. You set up a webhook, fire a test event, and\u0026hellip; nothing. The endpoint didn\u0026rsquo;t respond. Or it did but your handler crashed silently. Or you just want to see the exact payload the service sends before writing a single line of handler code.\nTools like RequestBin and Webhook.site exist. But I wanted to build my own — one I understood end to end, could deploy myself, and could use as a real product to learn distributed systems in practice. That\u0026rsquo;s HookSpy.\nThis is the full story — architecture decisions, a painful Vaadin mistake, 18 production bugs, and what I\u0026rsquo;d do differently.\nWhat HookSpy Does #You register, get a unique webhook URL like https://hookspy.in/h/{slug}, and point any service at it. Every incoming request appears in your dashboard in real time. You can inspect headers, body, query params — replay the request to any target URL — and export it as a ready-to-run cURL command.\nThree tiers:\nFree (1 endpoint, 100 requests/day), Pro (₹299/month, 10 endpoints, 30-day retention), Team (₹799/month, 50 endpoints, 90-day retention). The Architecture #Three Spring Boot microservices connected via Kafka.\nExternal Service (Stripe, Razorpay etc.) │ │ POST https://hookspy.in/h/{slug} ▼ ┌─────────────────────────────────────┐ │ capture-service :8080 │ │ Spring Boot WebFlux + Kafka Binder │ │ Returns 200 immediately │ │ Publishes to Kafka topic │ └─────────────────────────────────────┘ │ Kafka KRaft (no Zookeeper) ▼ ┌─────────────────────────────────────┐ │ processor-service :8081 │ │ @KafkaListener │ │ Validates tier limits │ │ Saves to PostgreSQL │ │ RetryableTopic (3 retries + DLT) │ └─────────────────────────────────────┘ │ PostgreSQL 16 ▼ ┌─────────────────────────────────────┐ │ ui-service :8082 │ │ Spring Boot REST + React 18 │ │ SSE live updates │ │ Auth, replay, Razorpay payments │ └─────────────────────────────────────┘ Why three services? #The most critical constraint: Stripe and Razorpay expect a 200 response within 5 seconds. If processing is slow, they retry — duplicate events. capture-service returns 200 immediately and hands off to Kafka. The caller never waits.\nWhy Kafka and not write directly to DB?\nI wanted to learn Kafka in a real context. But it\u0026rsquo;s also architecturally sound — capture can scale independently from the processor. Flash sale burst of webhooks? Kafka absorbs the spike. The processor works through the backlog at its own pace.\nKafka KRaft — no Zookeeper\nKafka\u0026rsquo;s KRaft mode removes the Zookeeper dependency entirely. One less service, one less thing to fail. The docker-compose setup is clean:\nkafka: image: confluentinc/cp-kafka:7.7.0 environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 Two different Kafka client styles — intentionally\ncapture-service uses Spring Cloud Stream Kafka Binder — a reactive Supplier\u0026lt;Flux\u0026gt; backed by Sinks.Many. The Binder wires it to the topic automatically. processor-service uses Spring Kafka directly — @KafkaListener with manual acknowledgement and @RetryableTopic for fine-grained reliability. Three retries with exponential backoff, then dead letter topic. Mixing both taught me where each approach shines — Binder for reactive fire-and-forget producers, plain Spring Kafka for consumers that need reliability control.\nThe Vaadin Mistake #I vibe coded the UI in Vaadin 24 first. Java all the way down, no context switching, built-in push support for live updates. Seemed reasonable.\nIt was a mistake.\nVaadin\u0026rsquo;s shadow DOM makes CSS control nearly impossible. Every rule needs !important. Dark mode is unreliable. Heavy JS bundle. The UI fought the framework constantly. After shipping the backend, I ripped out Vaadin entirely and rebuilt in React 18 + Vite + TailwindCSS. The frontend is a separate Vite project that builds into ui-service/src/main/resources/static/. Spring Boot serves React at / and the REST API at /api/*. One process in production, clean separation in development.\nThe migration took a week. The backend didn\u0026rsquo;t change at all — same entities, same services, same Kafka pipeline. Only ui-service changed.\nLesson: For any developer-facing product with design ambition, Vaadin will fight you the whole way. Start with React.\nSSE Instead of Polling #The Vaadin UI polled the database every 3 seconds. The React version uses Server-Sent Events.\nThe SSE endpoint holds a long-lived connection per slug. When processor-service saves a new webhook, a second Kafka consumer in ui-service receives the same event and pushes it to all open SSE emitters for that slug. New requests appear instantly.\n@GetMapping(value = \u0026#34;/endpoints/{slug}/requests/stream\u0026#34;, produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter stream(@PathVariable String slug) { var emitter = new SseEmitter(Long.MAX_VALUE); emitterRegistry.register(slug, emitter); // Send a keep-alive comment every 15s — prevents idle disconnect ScheduledFuture\u0026lt;?\u0026gt; ping = scheduler.scheduleAtFixedRate(() -\u0026gt; { try { emitter.send(SseEmitter.event().comment(\u0026#34;ping\u0026#34;)); } catch (IOException e) { emitter.completeWithError(e); } }, Instant.now(), Duration.ofSeconds(15)); emitter.onCompletion(() -\u0026gt; { emitterRegistry.remove(slug, emitter); ping.cancel(true); }); emitter.onTimeout(() -\u0026gt; { emitterRegistry.remove(slug, emitter); ping.cancel(true); }); emitter.onError((e) -\u0026gt; { emitterRegistry.remove(slug, emitter); ping.cancel(true); }); return emitter; } On the React side:\nuseEffect(() =\u0026gt; { const source = new EventSource( `/api/endpoints/${slug}/requests/stream`, { withCredentials: true } ) source.onmessage = (e) =\u0026gt; { const newReq = JSON.parse(e.data) queryClient.setQueryData([\u0026#39;requests\u0026#39;, slug], (old = []) =\u0026gt; [newReq, ...old]) } return () =\u0026gt; source.close() }, [slug]) SSE is underrated for developer tools. Simpler than WebSockets, perfect for one-directional live data. The only gotcha is nginx buffering — solved with proxy_buffering off.\n18 Bugs That Cost Me Time #1. CSRF Blocking Every POST Request #Every POST to /api/** returned 403.\nNo logs, no error. Spring Security\u0026rsquo;s default CSRF protection. One line fix:\n.csrf(csrf -\u0026gt; csrf.ignoringRequestMatchers(\u0026#34;/api/**\u0026#34;)) 2. SSE Leaking Content-Type Into Exception Handler #The SSE endpoint produces text/event-stream. Spring MVC cached this and applied it to error handler responses. GlobalExceptionHandler crashed with HttpMessageNotWritableException: No converter for ApiError with preset Content-Type \u0026rsquo;text/event-stream'.\nFix: write directly to HttpServletResponse, skip SSE requests entirely. Move SSE to its own controller class so the produces type doesn\u0026rsquo;t leak.\n3. Vite Proxy Hanging on SSE #Socket hang up errors on the SSE stream.\nVite\u0026rsquo;s HTTP proxy buffers responses — SSE frames were never flushed.\nFix: separate proxy rule for SSE endpoints with x-accel-buffering: no header, plus spring.mvc.async.request-timeout: -1 in application.yml.\n4. Razorpay Authentication Failed — Three Causes, Same Error #This one took the longest. Curl worked, SDK didn\u0026rsquo;t.\nCause 1: RazorpayClient instantiated at bean creation time before @Value injected. Fix: constructor parameters. Cause 2: application.yml had ${RAZORPAY_KEY_ID:placeholder} — the default value was overriding the actual env var. Fix: remove all defaults for production secrets. Cause 3: Invisible trailing whitespace in copy-pasted keys. Fix: .trim() on both values. 5. Razorpay JS SDK Undefined # tags inside React JSX don\u0026rsquo;t execute. window.Razorpay was always undefined.\nFix: load in index.html before React mounts.\n6. Razorpay Order Response Shape Wrong #Order created successfully but \u0026ldquo;could not initiate payment\u0026rdquo;. Backend was returning the raw Razorpay Order object — Jackson serialised it as the full Razorpay payload.\nFrontend expected { orderId, keyId } but got { id, amount, currency, \u0026hellip; }.\nFix: explicit Map.of(\u0026ldquo;orderId\u0026rdquo;, \u0026hellip;, \u0026ldquo;keyId\u0026rdquo;, \u0026hellip;) in the controller.\n7. @Value Null in Component #captureBaseUrl was null. The mapper was being instantiated with new MapperFromEntityToDTO() — Spring never touched it. @Value only works on Spring-managed beans. Always inject via constructor.\n8. Tier Lowercase Mismatch #Upgrade button not showing for FREE users. Backend returned \u0026ldquo;free\u0026rdquo;, frontend compared against \u0026ldquo;TEAM\u0026rdquo;.\nFix: user?.tier?.toUpperCase() everywhere.\n9. Docker Services Connecting to localhost #Inside a Docker network, services must use container service names, not localhost. application.yml had hardcoded localhost for DB and Kafka.\nFix: environment variable overrides in docker-compose.prod.yml:\nSPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${DB_NAME} SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092 10. Razorpay Env Vars Not Reaching Container #Could not resolve placeholder \u0026lsquo;RAZORPAY_KEY_ID\u0026rsquo;.\nDocker Compose environment: does variable substitution at compose level but doesn\u0026rsquo;t inject into the container.\nFix: add env_file: - .env to every service that needs secrets.\n11. nginx Certificate Not Found #nginx crash-looping. docker-compose.prod.yml had ./nginx/certs as cert path — certs were at /etc/letsencrypt.\nFix: mount the actual letsencrypt directory:\nvolumes: - /etc/letsencrypt:/etc/letsencrypt:ro 12. Wrong JAR Running in Container #ui-service logs showed Started CaptureServiceApplication.\nDockerfile used COPY target/*.jar app.jar — wildcard picked up the wrong JAR.\nFix: specific artifact name in each Dockerfile:\nCOPY target/ui-service-*.jar app.jar 13. Maven Not Found #mvn: command not found.\nIntelliJ uses a bundled Maven — system terminal doesn\u0026rsquo;t have it.\nFix: brew install maven.\n14. Maven Compiler Plugin 3.14.1 Bug With Java 21 + Lombok #ExceptionInInitializerError: TypeTag :: UNKNOWN. Known bug.\nFix: pin to 3.13.0, upgrade Lombok to 1.18.34.\n15. SCP Copying node_modules #File transfer taking 20+ minutes. scp -r copies everything.\nFix: use rsync with excludes:\nrsync -av --exclude=\u0026#39;node_modules\u0026#39; --exclude=\u0026#39;target\u0026#39; --exclude=\u0026#39;.git\u0026#39; ... 16. Docker Images Not Found on Server #hookspy/ui-service image not found. docker-compose.prod.yml used image: references — images were never pushed to a registry.\nFix: switch to build: with context pointing to each service directory.\n17. AuthenticatedUser Broke After Vaadin Removed #AuthenticationContext not found after removing Vaadin.\nFix: rewrite to use SecurityContextHolder:\nvar auth = SecurityContextHolder.getContext().getAuthentication(); 18. Registration Failing in Browser, Working via curl #No backend errors. Browser was serving a cached old JS bundle. Hard refresh (Cmd+Shift+R) resolved it.\nProduction deployment lesson: always hard refresh after deploying new frontend.\nProduction Stack # Layer Technology Language Java 21 Framework Spring Boot 4.0.5 Reactive producer Spring Cloud Stream Kafka Binder Reliable consumer Spring Kafka + @RetryableTopic Message broker Kafka KRaft (no Zookeeper) Database PostgreSQL 16 + Flyway ORM Spring Data JPA Auth Spring Security + BCrypt + HTTP Sessions Frontend React 18 + Vite + TailwindCSS Live updates Server-Sent Events (SSE) Payments Razorpay (HMAC-SHA256 verification) Reverse proxy nginx Hosting Hetzner CX23, Helsinki (~₹465/month) SSL Let\u0026rsquo;s Encrypt (auto-renewing) What I\u0026rsquo;d Do Differently #Start with React. The Vaadin migration week was completely avoidable.\nUse a proper internal event bus. ui-service has a second Kafka consumer just for SSE notifications. That\u0026rsquo;s processor-service\u0026rsquo;s concern leaking into ui-service. Better design: Spring ApplicationEvent for internal communication, Kafka as transport only.\nOne service, one responsibility — strictly. When a service grows a second Kafka consumer for a UI concern, that\u0026rsquo;s a smell worth stopping.\nRemove YAML default values for all secrets. ${SECRET:default} seems safe but the default silently wins when the env var isn\u0026rsquo;t loaded. Fail fast on missing secrets instead.\nTry It # HookSpy is live at https://hookspy.in — free tier, free forever.\nFollow along at @backendbrilliance.\nTechnical blog: dynamicallyblunttech.com\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/built-a-webhook-inspector-from-scratch-and-shipped-it-here-s-everything-that-went-wrong/","section":"Posts","summary":"\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eLive at:\u003c/strong\u003e \u003ca href=\"https://hookspy.in\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://hookspy.in\u003c/a\u003eHookSpy - Walk Through\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n\t\t\t\u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube.com/embed/FGn4FYMa6-U?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n\t\t\u003c/div\u003e\n\n\u003cp\u003eEvery developer integrating Stripe, Razorpay, or GitHub has been there. You set up a webhook, fire a test event, and\u0026hellip; nothing. The endpoint didn\u0026rsquo;t respond. Or it did but your handler crashed silently. Or you just want to see the exact payload the service sends before writing a single line of handler code.\u003c/p\u003e","title":"Built a Webhook Inspector from Scratch and Shipped It — Here's Everything That Went Wrong"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/hookspy/","section":"Tags","summary":"","title":"Hookspy"},{"content":"In this post, we will discuss on how to find sentences in a block of text, which can be a paragraph or an entire book. We will be using Apache OpenNLP library with Java.\nObjective here is to learn Apache OpenNLP library and effectively use it in processing any text. We will start with Sentence Segmentation and will post more on the usage of its services in further posts.\nApache Open NLP #Apache OpenNLP is an open source library, which is used to process Natural Language Text. It provides services such as sentence segmentation, tokenization, part of speech tagging, named entity extraction, chunking, parsing etc. Its a powerful library which comes with its own set of models, which I will be using in our examples. You can find the set of models from here http://opennlp.sourceforge.net/models-1.5/\nFor this post, we will setup our project and will start with Sentence Segmentation.\nMaven Dependencies #For using the Apache Open NLP library, we can either download the latest zip files from https://opennlp.apache.org/download.html. If you are downloading the zip files, once extracted, you have to set it in your classpath, else use maven dependencies in your project.\nAdd below maven dependencies in your project\n\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.opennlp\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;opennlp-tools\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.9.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.opennlp\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;opennlp-uima\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.9.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; Sentence Segmentation #The objective here is to find all the sentences in a given block of text, it could be paragraph or an entire book.\nWe will use one of the existing models en-sent.bin*, * to detect the sentences in the block of text. We will load the binary file in the ***InputStream ***and pass this stream object to ***SentenceModel ***class which belongs to opennlp.tools.sentdetect package. The model we used is converted to SentenceModel object.\n//Load the sentence detection model InputStream is = new FileInputStream(\u0026#34;src\\\\main\\\\resources\\\\models\\\\en-sent.bin\u0026#34;); SentenceModel modelObj = new SentenceModel(is); Now, load this model object to the SentenceDetectorME class which is also from same package. This class holds the methods which helps in finding the end of sentence character in a string. It splits the raw text to sentences.\nsentDetect() #This method will detect all the sentences from the given text.\n//Break raw text into sentences SentenceDetectorME detectorME = new SentenceDetectorME(modelObj); String[] detectedSen = detectorME.sentDetect(rawText); System.out.println(\u0026#34;All sentences detected are:\u0026#34;); System.out.println(Arrays.toString(detectedSen)); Once all the sentences are detected, we are printing it using the Arrays.toString() method. The output of the above print will be something like this\nAll sentences detected are: [Hi. How are you?, Hope everything is going well., Welcome to OpenNLP Lesson1., We will try to understand Open Apache NLP for sentence detection] sentPosDetect() #The ***SentenceDetectorME ***also provide sentPosDetect() method to get the position of the detected sentence. It return Span class which keep the start and end of the detected sentences. ***Span ***class had getStart() and getEnd() methods.\n//Find position of all sentences Span[] spans = detectorME.sentPosDetect(rawText); System.out.println(\u0026#34;Positions of all detected sentences:\u0026#34;); for(Span span: spans){ System.out.println(span); } The above part of code produce the following output:\nPositions of all detected sentences: [0..16) [17..47) [48..75) [76..140) getSentenceProbabilities() #It also have getSentenceProbabilities() method which gives the probabilities of each sentence for the most recent call to sentDetect(). If not applicable, then an empty array is returned.\n//Getting the probabilities of the last sendDetect method call for the sentences double[] probabilities = detectorME.getSentenceProbabilities(); System.out.println(Arrays.toString(probabilities)); The above code will produce the following output:\n[0.9240246995179983, 0.8808767380536497, 0.9899713160931164, 1.0] This is a very simple exercise to start with Apache Open NLP library. You can find the above code example in my Github repository. As of now, we are using the existing models which comes with Apache Open NLP only.\nFor the next step in learning to Natural Language Processing, .i.e. to tokenize any given string using Apache OpenNLP library is discussed in this post Tokenization Using Apache OpenNLP.\nReferences # https://opennlp.apache.org/docs/1.9.2/manual/opennlp.html https://opennlp.apache.org/docs/1.9.2/apidocs/opennlp-tools/index.html https://www.tutorialspoint.com/opennlp/index.htm http://opennlp.sourceforge.net/models-1.5/ Do let me know your thoughts if anything specific should be included and also do provide your comments/feedback down below.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/i-sentence-segmentation-using-apache-opennlp/","section":"Posts","summary":"\u003cp\u003eIn this post, we will discuss on how to find sentences in a block of text, which can be a paragraph or an entire book. We will be using \u003cem\u003e\u003cstrong\u003eApache OpenNLP\u003c/strong\u003e\u003c/em\u003e library with Java.\u003c/p\u003e\n\u003cp\u003eObjective here is to learn \u003cem\u003e\u003cstrong\u003eApache OpenNLP\u003c/strong\u003e\u003c/em\u003e library and effectively use it in processing any text. We will start with Sentence Segmentation and will post more on the usage of its services in further posts.\u003c/p\u003e\n\u003ch3 id=\"apache-open-nlp\" class=\"relative group\"\u003e\u003cstrong\u003eApache Open NLP\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#apache-open-nlp\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eApache OpenNLP is an open source library, which is used to process \u003cem\u003e\u003cstrong\u003eNatural Language Text\u003c/strong\u003e\u003c/em\u003e. It provides services such as sentence segmentation, tokenization, part of speech tagging, named entity extraction, chunking, parsing etc. Its a powerful library which comes with its own set of models, which I will be using in our examples. You can find the set of models from here \u003ca href=\"http://opennlp.sourceforge.net/models-1.5/\" target=\"_blank\" rel=\"noreferrer\"\u003ehttp://opennlp.sourceforge.net/models-1.5/\u003c/a\u003e\u003c/p\u003e","title":"I - Sentence Segmentation Using Apache  OpenNLP"},{"content":"In this blog post we will talk about one of the testing framework which I wanted to try it on, and rather use it in my day to day coding.\nMockito framework is the mocking framework that tastes really good.\nThis is from the official Mockito website, and I have not played with words here. :)\nBut as it says, it did taste good and you might extend your Junits with Mocks, because this is clean, readable and produce clean verification errors. Also it will become addictive.\nMockito framework mocks the objects by either\nUsing static methods, or Mocks annotations. Mockito provides\nAnnotation Support, Exception Support, Order support to run test cases. In this post, we will use annotations to mock the objects and will try to apply it in our existing project and learn a few key concepts associated to it.\nMocks and Stubs #Stubs can be defined as an clone of the instance with same attributes and behavior injected with our sample dataset, that can be used for testing.\nMocks is something which creates an image of the same class, and we can create the dataset as per the test cases.\nNeed for Mocks #When we have to write the Junits for our code\nWe need to identify the dependency like: database, webservices, other classes etc. and How many other Junits are accessing the same tables of that database. This would again reset or change the data consistency for our test criteria. Mockito comes with benefits of eliminating the dependency which would ease the creation of implementing our unit test cases. It would also keep the data consistency and integrity, because it will not change the data set used by other Junits test cases.\nAnother important aspects of using Mockito is to eliminate the external dependency on databases, or Webservices to be present for the execution of test. This will take care of the any slow IO operations also, if we use the Mockito which is part of the codebase.\nMaven Dependency #You can add the following maven dependency in your pom xml file.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mockito\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mockito-junit-jupiter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mockito\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mockito-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; Example #We will refer to our previous blog post on implementing Lambda and Functional progamming in Java 8, to implement different test cases. You can find the Github link here.\nHow to Write a Stub Class and Use it #When we talk about Stub, think this as a data structure which has some dummy data for my Junits to work on. So that, we don\u0026rsquo;t have to depend on the external database or webservices to return the dataset. A *stub *class can be created of your Entity, and you can add as many method required for the Junits written.\nI will use the User.java class from the previous post and will create a UserStub.java out of it. It will return dataset for its getter methods.\nFaker faker = new Faker(); public String getFirstName() { return faker.name().firstName(); } public String getLastName(){ return faker.name().lastName(); } public Integer getAge(){ return faker.number().numberBetween(1, 100); } In this example, I have used *javaFaker *third party tool, which creates random dummy dataset each time it is called. Easy and fun tool to use. You can refer the complete Stub class on my Github here.\nImplementing this *UserStub *class will be like any other utility/helper class invoked when the data is required. Then the assertion will be done based on the dataset provided.\nWe will refer to two Junits sortByAgeDescAndNameAsc() and getAverageAgeOfUsers() here\n@Test public void sortByAgeDescAndNameAsc() { List\u0026lt;User\u0026gt; users = userStub.getDummyUsersWithAllPrivileges(); List\u0026lt;User\u0026gt; sortedUsers = userService.sortByAgeDescAndNameAsc(users); assertEquals(50, sortedUsers.get(0).getAge()); assertEquals(\u0026#34;dynamically\u0026#34;, sortedUsers.get(0).getFirstName()); assertEquals(\u0026#34;blunt\u0026#34;, sortedUsers.get(0).getLastName()); assertEquals(32, sortedUsers.get(2).getAge()); assertEquals(\u0026#34;dynamically\u0026#34;, sortedUsers.get(2).getFirstName()); assertEquals(\u0026#34;blunttech\u0026#34;, sortedUsers.get(2).getLastName()); } @Test public void getAverageAgeOfUsers(){ List\u0026lt;User\u0026gt; users = userStub.getDummyUsersWithAllPrivileges(); assertEquals(38, userService.getAverageAgeForUsers(users)); } Both the Junits, is dependent on the *Stub *class method to create the dataset, which is further used for the testing.\nMajor Disadvantages Using Stub is high maintenance.\nFor every method added, a list of dataset that can be created for the Stub need to be handled, which increases the complexity when the application grows in size.\nSo, instead of using Stubs, we will be focusing on *Mock *objects now.\nHow to write Junits with Mocks #Creation of Mock Object\nAs stated above there are two way to instantiate the Mock object.\nby directly calling the Mockito.mock() method by calling @Mock annotations. When using Mock annotation, it is mandatory to call MockitoAnnotations.openMocks() to initialize the mock objects. MockitoAnnotations.openMocks() initializes fields annotated with Mockito annotations. It also\nAllows shorthand creation of objects required for testing. Minimizes repetitive mock creation code. Makes the test class more readable. Makes the verification error easier to read because field name is used to identify the mock. But, there is an integration change when working with Junit 5. In Junit 4, there was @RunWith annotation, which will take the MockitoJunitRunner.StringStub.class and instantiate the mock object with its value.\nNow the @RunWith annotation is removed in Junit 5 and therefore cannot be integrated like above. Instead, Junit 5 has more powerful @ExtendWith annotation, which will take the MockitoExtension.class that comes as part of mockito-junit-jupiter dependency.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mockito\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mockito-junit-jupiter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.4.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; It is not mandatory to using @ExtendWith, instead the MockitoAnnotation.openMocks() can be used to instantiate the mock object.\nNow, we will try simple, precise and different flavors of Junits tests created using Mockito framework. TDD*(Test Driven Development) *approach can be achieved easily but since I reuse already written code, we will not use ***TDD ***here.\nWhen \u0026hellip; Then #Enables stubbing methods. Think of this as *when a method is called then do this*. e.g. when doSomething() is called, then return String.\nWe can implement this for our sortAgeDescNameAsc() method\nwhen( userService.sortByAgeDescAndNameAsc(new ArrayList\u0026lt;\u0026gt;())) .thenReturn(users); This states when sortByAgeDescAndNameAsc() method is called with List as an argument, then it will return List.\nIterator Style Stubbing #There will be scenarios where the method would return multiple values and we have to assert on each value it is returning.\nThis can be asserted by calling thenReturn() method multiple times. Every time it is called, it will move on to return the next value. It will behave something like Iterator.next() method.\nWe will implement Junit for the getUpdateUserwithAgeHigherThan() method from out previous blog post. Here if we have multiple values of the same age, it would return me different value on each thenReturn() call.\nList\u0026lt;User\u0026gt; users = Arrays.asList( new User(faker.number().randomNumber(), \u0026#34;Dynamically\u0026#34;, faker.name().lastName(), faker.number().numberBetween(30, 100), Arrays.asList(Privilege.CREATE, Privilege.UPDATE)), new User(faker.number().randomNumber(), \u0026#34;Blunt-tech\u0026#34;, faker.name().lastName(), faker.number().numberBetween(30, 100), Arrays.asList(Privilege.CREATE, Privilege.UPDATE, Privilege.READ)), new User(faker.number().randomNumber(), faker.name().firstName(), faker.name().lastName(), faker.number().numberBetween(1, 100), Arrays.asList(Privilege.READ)) ); when(userService.getUpdateUserWithAgeHigherThan(anyList(), anyInt())) .thenReturn(Optional.ofNullable(users.get(0))) .thenReturn(Optional.ofNullable(users.get(1))); For the given list of Users, where age=30 is for two User objects, When..Then will return 0th index element first and if called again, it will return 1st index value.\nWhen we will do the assertion like this\nassertEquals(\u0026#34;Dynamically\u0026#34;, userService .getUpdateUserWithAgeHigherThan(Collections.emptyList(), 30) .get() .getFirstName()); assertEquals(\u0026#34;Blunt-tech\u0026#34;, userService .getUpdateUserWithAgeHigherThan(new ArrayList\u0026lt;\u0026gt;(), 30) .get() .getFirstName()); First assert statement will return *true *because getUpdateUserWithAgeHigherThan() will return 0th index value.\nWhile the second assert statement will also return *true *again, because getUpdateUserWithAgeHigherThan() will return the value at the 1st index.\nArgument Matchers #From the above example, notice that the method getUpdateUserWithAgeHigherThan() is called with two arguments which is Mockito.anyXXX(). This allows us to pass any dummy value to the matcher functions. Like here for the first argument, it would work even if a\nCollections.emptyList() is called, or new ArrayList\u0026lt;\u0026gt;() is called, or new LinkedList\u0026lt;\u0026gt;() is called etc. Handling Exceptions #Lets suppose for arguments you are passing *null *and this would throw *NullPointerException *in future. So, we can handle these scenarios using thenThrow().\nIf for the above method getUpdateUserWithAgeHigher() we pass List as null, which should cause exception in the code implementation. We can write our Junit like this:\nwhen(userService.getUpdateUserWithAgeHigherThan(null, 30)) .thenThrow(RuntimeException.class); This assertion will return true only if the code is throwing RuntimeException. Lets say for this Junit, the code is not throwing RuntimeException, then the assertion will fail with the message passed as the third argument to assertThrows().\nassertThrows( RuntimeException.class, () -\u0026gt; {userService.getUpdateUserWithAgeHigherThan(null, 30);}, \u0026#34;Users list is empty. RuntimeException expected!!\u0026#34;); BDD with Mockito #Behavior Driver Development style of writing tests uses given\u0026hellip;when\u0026hellip;then design as fundamental part of the test methods. The following statement is from the Java Doc of Mockito.\nThe problem with current stubbing api with canonical role of when* word does not integrate nicely with given\u0026hellip;when\u0026hellip;then comments. It\u0026rsquo;s because stubbing belongs to given component of the test and not to the when component of the test. Hence BDDMockito class introduces an alias so that you stub method calls with given(Object) method. Now it really nicely integrates with the given *component of a BDD style test!\n*BDDMockito *is imported to work on implementing a more natural order of test creation and execution. We will implement Junit test for getMostFrequentLastName() using the BDDMockito.\ngiven(userService.getMostFrequentLastName(anyList())) .willReturn(Optional.of(lastName)); Here, we are defining the behavior which is\nif given with the getMostFrequentLastName() then return *Optional *of the lastName string. The given\u0026hellip;when\u0026hellip;.then will looks like this\n// Defined Expected Behaviour given(userService.getMostFrequentLastName(anyList())).willReturn(Optional.of(lastName)); // When method is called // First Invocation userService.getMostFrequentLastName(Collections.emptyList()).get(); // Then Verify, how many times it is called. verify(userService, times(1)).getMostFrequentLastName(anyList()); Verify #Mockito provides verify method which identifies that how many times/ never/ atleast/ atmost the actual method is invoked. These are different flavors and will go through these one by one.\nLet\u0026rsquo;s take the above example, where we have getMostFrequentLastName() and will verify the number of times it was invoked/called using times() which takes expected number of times as argument.\n// Then Verify, how many times it is called. verify(userService, times(1)).getMostFrequentLastName(anyList()); Here times(1) is the default value and can be omitted.\nWe can use the atLeastonce() or* atMostOnce()* method also, because it would identify that the method is invoked for at least 1 time or for at most 1 time respectively.\n// Returns true, as method is invoked at-least once. verify(userService, atLeastOnce()).getMostFrequentLastName(anyList()); // Return true, as method is invoked max of 1 time. verify(userService, atMostOnce()).getMostFrequentLastName(anyList()); Similarly, we can use atLeast(n) or atMost(n), which takes how many times it was expected to be invoked.\n// Second Invocation userService.getMostFrequentLastName(anyList()); // Return true, as method is invoked at-least 2 times. verify(userService, atLeast(2)).getMostFrequentLastName(anyList()); For this example, we have never called getAverageAgeForUsers(), and so it was never invoked, which can be asserted like this\n// Return true, as given method is never invoked. verify(userService, never()).getAverageAgeForUsers(any()); Void Return Type #When working with methods which have *void *as return type, we can use the doReturn() method of Mockito. It would be something like\ndoReturn(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;).when(mockObj).doSomething() This will return list of strings, when the doSomething() is called on the Mock object. The below statement is from the Java doc of Mockito.\nYou can use doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() in place of the corresponding call with when(), for any method. It is necessary when you\nstub void methods stub methods on spy objects stub the same method more than once, to change the behavior of a mock in the middle of a test. There are other topics which include *spying *on the real objects, mocking static, final, enums, lenient etc.. but adding here will make this post like a mini book. This could be added as another post in future.\nOnce you have read about the above ways to create Junits, then it is advisable to refer to this JavaDoc where everything that can be achieved using Mockito is listed with how to implement it. I would also recommend going through the *Github *example for the Mocks and try to create as many testcases in your project. It will help in knowing your code better and implementing different features with ease testing all dependent features.\nHope this will help you in your learning process. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/junit5-with-mockito-framework/","section":"Posts","summary":"\u003cp\u003eIn this blog post we will talk about one of the testing framework which I wanted to try it on, and rather use it in my day to day coding.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eMockito framework is the mocking framework that tastes really good.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThis is from the official Mockito \u003ca href=\"https://site.mockito.org/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003ewebsite\u003c/em\u003e\u003c/a\u003e, and I have not played with words here. :)\u003c/p\u003e\n\u003cp\u003eBut as it says, it did taste good and you might extend your Junits with Mocks, because this is clean, readable and produce clean verification errors. Also it will become addictive.\u003c/p\u003e","title":"Junit5 with Mockito Framework"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/kafka/","section":"Categories","summary":"","title":"Kafka"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/kafka/","section":"Tags","summary":"","title":"Kafka"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/opennlp/","section":"Categories","summary":"","title":"OpenNLP"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/react/","section":"Categories","summary":"","title":"React"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/reactive/","section":"Categories","summary":"","title":"Reactive"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/reactive/","section":"Tags","summary":"","title":"Reactive"},{"content":"Harnessing the power of reactive programming in Java opens up endless opportunities for crafting responsive, resilient applications. With an emphasis on asynchronous data streams, this approach allows you to manage system resources efficiently while enhancing user experiences. In this blog post, we\u0026rsquo;ll explore two leading frameworks—RxJava and Spring Reactive—sharing specific scenarios to illustrate practical reactive programming techniques.\nUnderstanding Reactive Programming #Reactive programming promotes a non-blocking data flow, enabling components to respond to events as they occur. This approach is crucial for modern web applications, which demand real-time capabilities. By leveraging the observer pattern, reactive programming allows your application to respond to streams of data swiftly, enhancing user interactivity.\nIn Java, libraries such as RxJava and Spring Reactive harness the principles of reactive programming to facilitate seamless event handling. These frameworks enable backend developers to build systems that can scale effectively, perform optimally, and react quickly to user interactions. For example, reactive programming can handle thousands of simultaneous user connections without a hitch, maintaining performance and reliability.\nRxJava: Embracing Reactive Streams #Using RxJava, let\u0026rsquo;s examine an example that highlights its advantages.\nExample 1: Real-Time Data Processing #Imagine you need to collect and process user activity logs in real time. Here’s how you might set this up using RxJava.\nObservable\u0026lt;UserAction\u0026gt; userActions = Observable.create(emitter -\u0026gt; { // Simulating user actions being emitted from a source while (!emitter.isDisposed()) { UserAction action = getUserAction(); if (action != null) { emitter.onNext(action); } } }); userActions .filter(action -\u0026gt; action.isCritical()) .observeOn(Schedulers.io()) .subscribe(action -\u0026gt; handleCriticalAction(action), Throwable::printStackTrace); In this scenario, we create an observable that emits user actions. By using a filter operator, we isolate critical actions for processing. Employing observeOn directs the emitted items to the IO Scheduler, allowing for parallel processing and improving performance significantly—potentially reducing response times by 30 to 50%.\nThis concise model showcases the power of RxJava, demonstrating how you can filter and respond to significant events without overloading your application.\nSpring Reactive: Building Asynchronous Web Applications #Switching to Spring, the Spring WebFlux framework is exceptionally suited for building reactive web applications. Let’s look at a practical example involving a RESTful service designed to handle requests asynchronously.\nExample 2: Asynchronous REST Service #Suppose you need to fetch user profiles from a database and return them via a REST API. With Spring Reactive, this can be done smoothly:\n@RestController @RequestMapping(\u0026#34;/api/users\u0026#34;) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping(\u0026#34;/{id}\u0026#34;) public Mono\u0026lt;UserProfile\u0026gt; getUserProfile(@PathVariable String id) { return userService .findUserById(id) .subscribeOn(Schedulers.boundedElastic()); } } In this example, Mono\u0026lt;UserProfile\u0026gt; signifies a single user profile fetched asynchronously. Spring WebFlux’s reactive support enables requests to be processed on a different thread, allowing the main thread to manage additional incoming requests swiftly. This setup can increase the throughput of your application by 40%, particularly during peak usage.\nIntegrating a reactive approach in REST APIs simplifies error handling and leads to better resource utilization, ultimately enhancing application scalability.\nAdvantages of Reactive Programming #The adoption of reactive programming principles transforms backend system operations. Here are a few benefits:\nScalability: By facilitating non-blocking operations, reactive programming enables your system to handle higher loads without a proportional increase in resource allocation. For instance, systems can manage web traffic spikes of up to 75% more without crashing.\nResponsive Performance: Applications built with reactive frameworks can respond to events in real time, providing users with a more dynamic interaction layer. Response times can improve drastically, potentially cutting down waiting periods by 50%.\nImproved Resource Management: Asynchronous processing helps avoid thread exhaustion, optimizing the use of system resources.\nFinal Thoughts #As the need for real-time and high-performance applications continues to grow, embracing advanced reactive programming techniques is essential for backend developers. The reactive programming paradigm, equipped with tools like RxJava and Spring Reactive, paves the way for building responsive and robust systems.\nBy incorporating the examples provided into your development practices, you take an important step toward mastering reactive programming in Java. Continuously refining these methods will not only enhance your applications but also streamline your overall development process.\nFeel free to explore, experiment, and leave your comments below! Happy coding!\nNOTE: This entire post which you have read is entirely generated by AI, where I have asked it to create a blog post with examples under 1000 words for a technical audience. Include relatable images. It has added the images but those were not related, so removed it. Hopefully a better prompt would generate even better blog post now.\nHappy learning or should I say prompting !! #","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/reactive-programming-in-java-with-spring-ai-generated/","section":"Posts","summary":"\u003cp\u003eHarnessing the power of reactive programming in Java opens up endless opportunities for crafting responsive, resilient applications. With an emphasis on asynchronous data streams, this approach allows you to manage system resources efficiently while enhancing user experiences. In this blog post, we\u0026rsquo;ll explore two leading frameworks—RxJava and Spring Reactive—sharing specific scenarios to illustrate practical reactive programming techniques.\u003c/p\u003e\n\u003ch2 id=\"understanding-reactive-programming\" class=\"relative group\"\u003eUnderstanding Reactive Programming \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#understanding-reactive-programming\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eReactive programming promotes a non-blocking data flow, enabling components to respond to events as they occur. This approach is crucial for modern web applications, which demand real-time capabilities. By leveraging the observer pattern, reactive programming allows your application to respond to streams of data swiftly, enhancing user interactivity.\u003c/p\u003e","title":"Reactive Programming in Java with Spring (AI Generated)"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/webhook/","section":"Tags","summary":"","title":"Webhook"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/%23105/","section":"Tags","summary":"","title":"#105"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/%23983/","section":"Tags","summary":"","title":"#983"},{"content":"In this post we will create a binary tree from the given in-order and pre-order traversal. You can find the details on the question here leetcode #105.\nSo, in a binary tree, we can do depth first traversal in three ways:\n***In-order traversal (left -\u0026gt; root -\u0026gt; right) *** ***Pre-order traversal (root -\u0026gt; left -\u0026gt; right) *** ***Post-order traversal (left -\u0026gt; right -\u0026gt; root) *** Refer Binary Tree - Traversal to understand on how to traverse using the above three ways.\nProblem Statement #The problem states to construct the binary tree with the given pre-order traversal and in-order traversal.\nInput:\npre-order : [4, 2, 1, 3, 7, 6, 9] in-order : [1, 2, 3, 4, 6, 7, 9] Output:\n4 / \\ 2 7 / \\ / \\ 1 3 6 9 Algorithm/Logic #Using the pre-order traversal, we can deduce that the first node in the pre-order array will be the root of the binary tree, we are going to construct.\nFurther, now we will look in the in-order traversal for the root and whatever comes on the left side of the root in the in-order traversal will be the nodes present on the left side of the root node and whatever comes on the right side of the root in the in-order traversal will be on the right side of the root node.\nFrom the first node in the pre-order traversal, we know that 4 is the root node of our binary tree. We will find the root node in in-order and divide the left nodes and right nodes.\n4 / \\ 1,2,3 6,7,9 Now, for the second round of recursion, we will take the left node list and perform the same approach as applied to find the first level of the binary tree.\nFrom the left node list (1, 2, 3), the root node will be 2 which is the next node after 4 in the pre-order traversal. Now, in the in-order traversal, nodes to the left of the new root node 2 is 1 and to the right of root node 2 is 3. So, we have the left nodes set for our binary tree.\n4 / \\ 2 6,7,9 / \\ 1 3 Using the same approach, we can construct the rights nodes of our binary tree and can construct the entire binary tree.\n4 / \\ 2 7 / \\ / \\ 1 3 6 9 Code-snippet #We will add the lombok dependency to avoid any boilerplate code. If you are just writing the program without using maven or gradle, then you can skip adding this dependency and instead just add the constructor, getter and setter methods.\n\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.18.22\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; For any TreeNode, we will have the left node, right node and the value of the node.\n@Data public class TreeNode { private TreeNode left; private TreeNode right; private int intValue; } The @Data annotation will add the required constructors, getters and setters method for all these attributes.\nWith this, we will need a utility to find the root node in the in-order traversal in the given index range.\nprivate int getNodeIndexInIn(int node, int[] inOrder, int start, int end){ int index = 0; for(int i=start; i\u0026lt;=end; i++){ if(node==inOrder[i]){ index = i; return index; } } return index; } Now we will use recursion to create my left sub tree and then my right sub tree. To keep track on the node which will be our root for sub-trees, we will keep a global index variable.\n***NOTE: ***Always be extra careful for the exit criteria when you write programs using recursion. These exit points are the key in stopping the recursion. Else you will get StackOverflow error.\nThere will be two exit criteria defined for our recursion:\nfirst:\n//1st Exit Criteria: Return null, when the indexes are crossing each other, means no value can be found in the array if(inStart \u0026gt; inEnd){ return null; } Second:\n//Second exit criteria: When the node is a leaf node, means no left or right node exists, then return the node if (inStart == inEnd) { return rootNode; } To construct our left and right subtree it will call the recursively with proper start and end index with the correct root index for our subtrees.\n//Find the rootIndex in Inorder array int rootIndexInInorder = getNodeIndexInIn(root, in, inStart, inEnd); rootNode.setLeft(constructBTree(\u0026#34;left\u0026#34;, inStart, rootIndexInInorder - 1, in, pre)); rootNode.setRight(constructBTree(\u0026#34;right\u0026#34;, rootIndexInInorder + 1, inEnd, in, pre)); So finally the entire code of recursion now will look like this:\n//This will be used for get the root index in preOrder private static int preRootIndex = 0; public TreeNode constructBTree(String side, int inStart, int inEnd, int[] in, int[] pre){ //1st Exit Criteria: Return null, when the indexes are crossing each other, means no value can be found in the array if(inStart \u0026gt; inEnd){ return null; } int root = pre[preRootIndex++]; TreeNode rootNode = new TreeNode(root); //Second exit criteria: When the node is a leaf node, means no left or right node exists, then return the node if (inStart == inEnd) { return rootNode; } //Find the rootIndex in Inorder array int rootIndexInInorder = getNodeIndexInIn(root, in, inStart, inEnd); rootNode.setLeft(constructBTree(\u0026#34;left\u0026#34;, inStart, rootIndexInInorder - 1, in, pre)); rootNode.setRight(constructBTree(\u0026#34;right\u0026#34;, rootIndexInInorder + 1, inEnd, in, pre)); return rootNode; } The code for the above program can be found here Github Link\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/lc105-binary-tree-from-in-order-and-pre-order-traversal/","section":"Posts","summary":"\u003cp\u003eIn this post we will create a binary tree from the given in-order and pre-order traversal. You can find the details on the question here \u003ca href=\"https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003e\u003cstrong\u003eleetcode #105\u003c/strong\u003e\u003c/em\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eSo, in a binary tree, we can do depth first traversal in three ways:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e***In-order traversal  (left -\u0026gt; root -\u0026gt; right) ***\u003c/li\u003e\n\u003cli\u003e***Pre-order traversal  (root -\u0026gt; left -\u0026gt; right) ***\u003c/li\u003e\n\u003cli\u003e***Post-order traversal  (left -\u0026gt; right -\u0026gt; root) ***\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eRefer \u003ca href=\"https://www.dynamicallyblunttech.com/post/binary-tree-traversal\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003e\u003cstrong\u003eBinary Tree - Traversal\u003c/strong\u003e\u003c/em\u003e\u003c/a\u003e to understand on how to traverse using the above three ways.\u003c/p\u003e","title":"#LC105 - Binary Tree from In-Order and Pre-Order Traversal"},{"content":"In this post we will go through the Leetcode #983 to find the minimum cost for Tickets, which could be one of the starting practice programs in Dynamic Programming. This is a really interesting problem and I took help from Nideesh Terapalli youtube video.\nProblem Statement #In a country popular for train travel, you have planned some train travelling one year in advance. The days of the year that you will travel is given as an array days. Each day is an integer from 1 to 365.\nTrain tickets are sold in 3 different ways:\na 1-day pass is sold for costs[0] dollars; a 7-day pass is sold for costs[1] dollars; a 30-day pass is sold for costs[2] dollars. The passes allow that many days of consecutive travel. For example, if we get a 7-day pass on day 2, then we can travel for 7 days: day 2, 3, 4, 5, 6, 7, and 8.\nReturn the minimum number of dollars you need to travel every day in the given list of days.\nLogic/ Algorithm: #As the problem states, that we need to find the travel cost of a planned year, where I know the days of travel. Now to minimize the cost of the travel, I have to take the pass in such a way that the total cost is minimum.\nWe will divide the problem in three parts:\nI know the days I am travelling,\nI know that for the day I am going to travel there are three option, either I will pay for a day, or for a week, or for a month.\nFrom the problem, we can find the last day I traveled, by getting the last element of the array days.\nNow we can deduce from the above, cost of travel for the days I don\u0026rsquo;t travel will be whatever is the cost of the last day. Whether\nIf the last day falls under single day pass, then I will be paying costs[0], or If the last day falls under seven day pass, then I will be paying costs[1], or If the last day falls under thirty days pass, then I will be paying costs[2]. Now, lets dig into the code.\nCode #To find the last day of my travel prepare two arrays\ncost of each day, and all the travel days. int lastDayForTravel = days[days.length-1]; boolean[] travelDays = new boolean[lastDayForTravel+1]; int[] costOfEachDay = new int[lastDayForTravel+1]; Prepare the travelDays array which is a boolean array where days I traveled is marked True.\nfor(int day: days){ travelDays[day] = true; } Iterate over our resulting array *costOfEachDay, *to set the cost of travel for each day\nfor(int i=1; i\u0026lt;costOfEachDay.length; i++){ if(!travelDays[i]){ costOfEachDay[i] = costOfEachDay[i-1]; continue; } else { int boughtSingleDay = costOfEachDay[i-1] + cost[0]; int boughtSevenDays = costOfEachDay[Math.max(i-7, 0)] + cost[1]; int boughtThirtyDays = costOfEachDay[Math.max(i-30, 0)] + cost[2]; int temp = Math.min(boughtSingleDay, boughtSevenDays); costOfEachDay[i] = Math.min(temp, boughtThirtyDays); } } Return the final answer which will be the last element of the array costOfEachDay\nreturn costOfEachDay[lastDayForTravel]; You can find the entire code base here on my github repository. This is a good starting point for Dynamic Programming. More post coming-in on DP 😎\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/lc983-find-the-minimum-cost-for-tickets-dp/","section":"Posts","summary":"\u003cp\u003eIn this post we will go through the \u003ca href=\"https://leetcode.com/problems/minimum-cost-for-tickets/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eLeetcode #983\u003c/em\u003e\u003c/a\u003e to find the minimum cost for Tickets, which could be one of the starting practice programs in \u003cem\u003e\u003cstrong\u003eDynamic Programming\u003c/strong\u003e\u003c/em\u003e. This is a really interesting problem and I took help from \u003ca href=\"https://www.youtube.com/watch?v=HyN5hdD1JU4\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eNideesh Terapalli\u003c/em\u003e\u003c/a\u003e youtube video.\u003c/p\u003e\n\u003ch3 id=\"problem-statement\" class=\"relative group\"\u003e\u003cstrong\u003eProblem Statement\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem-statement\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eIn a country popular for train travel, you have planned some train travelling one year in advance.  The days of the year that you will travel is given as an array days.  Each day is an integer from 1 to 365.\u003c/p\u003e","title":"#LC983 - Find The Minimum Cost For Tickets (DP)"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/aes/","section":"Categories","summary":"","title":"AES"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/aes/","section":"Tags","summary":"","title":"AES"},{"content":"In this post we will encrypt and decrypt a given phrase using AES algorithm in GCM mode. This will be more on how to do it. I am not an expert on ciphers, but a basic way of how would you achieve the encryption/decryption will help in securing your applications.\nAES Algorithm # Advanced Encryption Standard(AES) is a symmetric, block cipher encryption/decryption algorithm.\nBy *symmetric *here, it means that the algorithm uses a secret key, which is used for encryption and decryption. So without the secret key entire encryption/decryption will fail.\nBy block cipher, it means that the algorithm converts the plain text data into blocks which is then encrypted/decrypted.\nAES algorithm is based on substitution-permutation principle, which takes the blocks of plain text and key as input and apply multiple **rounds **of Substitution and then *Permutation *on it, to produce the encrypted text.\nThe key size specifies the number of **transformation **rounds that convert the plaint text to cipher text. The reverse rounds are applied to transform the cipher text to plain text (decryption).\nThe number of rounds for each key size is as follows:\nRounds Key Size 10 128 bits 12 192 bits 14 256 bits A *transformation *is a string that defines the set of operations performed to convert the plain text to cipher text. A transformation string will always include the name of the algorithm (like AES) and may be followed by a feedback mode and padding scheme.\ne.g. AES/CBC/PKCS5Padding\nFeedback Modes #Most commonly used modes is CBC with PKCS5Padding. And since CBC doesn\u0026rsquo;t use the streams of cipher, GCM (Galois/Counter Mode) is preferred.\nThese below information is from the Cipher class documentation in Java.\nUsing modes such as CFB(Cipher Feedback) and OFB(Output feedback), block ciphers can encrypt data in units smaller than the cipher\u0026rsquo;s actual block size. When requesting such a mode, you may optionally specify the number of bits to be processed at a time by appending this number to the mode name as shown in the \u0026ldquo;AES/CFB8/NoPadding\u0026rdquo; and \u0026ldquo;AES/OFB32/PKCS5Padding\u0026rdquo; transformations. If no such number is specified, a provider-specific default is used. Thus, block ciphers can be turned into byte-oriented stream ciphers by using an 8 bit mode such as CFB8 or OFB8.\nModes such as Authenticated Encryption with Associated Data (AEAD) provide authenticity assurances for both confidential data and Additional Associated Data (AAD) that is not encrypted. Both confidential and AAD data can be used when calculating the authentication tag (similar to a Mac). This tag is appended to the ciphertext during encryption, and is verified on decryption.\nAEAD modes such as GCM/CCM perform all AAD authenticity calculations before starting the ciphertext authenticity calculations. To avoid implementations having to internally buffer ciphertext, all AAD data must be supplied to GCM/CCM implementations (via the updateAAD methods) before the ciphertext is processed (via the update and doFinal methods).\nNote that GCM mode has a uniqueness requirement on IVs used in encryption with a given key. When IVs are repeated for GCM encryption, such usages are subject to forgery attacks. Thus, after each encryption operation using GCM mode, callers should re-initialize the cipher objects with GCM parameters which has a different IV value.\nImplementation #Let\u0026rsquo;s look into the implementation of the AES algorithm with GCM mode.\nYou can download the cipher zip supported by Java from JCE Unlimited Strength Policy.\nEncryption #The Initialization vector(IV) size we will use is 16 bytes, which is equivalent to 16 * 8 = 128 bits. We will use *SecureRandom *class to initialize random bytes to Initialization Vector(IV).\nbyte IV[] = new byte[IV_SIZE]; SecureRandom secRandom = new SecureRandom() ; secRandom.nextBytes(IV); We have to get the cipher instance of the transformations we want, here we will use AES/GCM/NoPadding. This should be same for encryption and decryption.\nCipher cipher = Cipher.getInstance(\u0026#34;AES/GCM/NoPadding\u0026#34;); This cipher class will be used to add the authentication tag data. For authentication tag data, use a byte array. We have used this\nbyte[] aadTagData = \u0026#34;dynamicallyblunttech\u0026#34;.getBytes() ; We will then need the *GCMParameterSpec *object which sets the necessary parameters required for GCM mode.\nIn CBC mode, it only requires an Initialization Vector(IV) such as *IvParameterSpec, *but in GCM mode, it requires the IV and the tLen which is the length of authentication tag(in bits).\npublic static final int GCM_TAG_LENGTH = 128; GCMParameterSpec gcmParameterSpec = new GCMParameterSpec( GCM_TAG_LENGTH, IV); We have to create the SecretKey which will be used in encryption and decryption of our passwords. It will take String as an input and uses SHA-256 as Message Digest instance.\nMessage Digest is a secure one way hash function which takes arbitrary length data and generates fixed sized hash value. It uses Message Digest algorithm such as MD5 or SHA-1 or SHA-256\nkey = secretKey.getBytes(\u0026#34;UTF-8\u0026#34;); MessageDigest sha = MessageDigest.getInstance(\u0026#34;SHA-256\u0026#34;); key = sha.digest(key); key = Arrays.copyOf(key, 16); sks = new SecretKeySpec(key, \u0026#34;AES\u0026#34;); return sks; Now that we have all the things required to initialize my Cipher instance. And the Cipher.init() method takes the\nmode (1-Encrypt, 2-Decrypt) Key (Secret Key) Parameter Set (GCMParameterSpec) SecureRandom (instance) cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec, secRandom); Once the cipher is initialized with above values, we have to update the plain text as byte[] into the Cipher instance. But before updating the plain text, we need to update Authentication Tag using updateAAD() method of Cipher instance.\ncipher.updateAAD(aadTagData); cipher.update(bytePassword); After this, call the doFinal() method of Cipher instance, which will return a byte array of encrypted string.\nbyte[] encryptedBytes = cipher.doFinal(); Now majorly two important points here:\nIn most of the working code, you will be dealing with character array and not Strings. Since String pose security risk as its an immutable object and cannot be changed, rather a change will create a new String. But if we use the character array, we can initialize the value to empty to some random value after the sensitive data is not required. So, how to convert the character array to byte array without missing any bytes ? Another point is, now we have the secret key, IV which will be required to decrypt the message. Where do we store them ? Storing it in disk space pose another security risk of the key to be damaged or lost or used by attackers to identify the message. We can convert the character array to byte array by using ByteBuffer class.\nByteBuffer byteBuffer = Charset.defaultCharset() .encode(CharBuffer.wrap(toEncrypt)); byte[] bytePassword = new byte[byteBuffer.remaining()]; byteBuffer.get(bytePassword); And to store the encrypted message and IV so that we can use it during decryption, we can append the IV to the encrypted message and encode the string using Base64 again.\nbyte[] ivCTAndTag = new byte[IV.length + encryptedBytes.length]; System.arraycopy(IV, 0, ivCTAndTag, 0, IV.length); System.arraycopy(encryptedBytes, 0, ivCTAndTag, IV.length, encryptedBytes.length); Base64.getEncoder().encodeToString(ivCTAndTag); Above code is to create a byte array of combined size of IV and the encrypted String. And then copy the IV and encrypted bytes to the new array. To even add more complexity, you can do some other operations (like introducing extra bytes in some order etc) here for more security.\nSince the way we used to create the secret key will provide the same key, we can create a random text given as input. Thus, for every deployment of you application, it will generate a different input key for the generateSecretKey() method and thus different secret key.\nDecryption #We will use the same algorithm transformations to create a new Cipher instance. Decode our message and then extract the IV bytes and encrypted message from the encrypted bytes.\nWe will follow the same way, where we create the *GCMParameterSpec *instance using the extracted IV, initialize the Cipher instance with *Decrypt *mode, the secret key, *GCMParameterSpec *instance and the *SecureRandom *instance.\nThen will update the authentication tag for the Cipher instance and update the encrypted bytes. Finally, use the doFinal() to get the decrypted bytes, which can be converted to String.\nCipher cipher = Cipher.getInstance(\u0026#34;AES/GCM/NoPadding\u0026#34;); byte[] decodedToDecrypt = Base64.getDecoder().decode(toDecrypt); byte[] IV = new byte[IV_SIZE]; System.arraycopy(decodedToDecrypt, 0, IV, 0, IV.length); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, IV); byte[] encryptedBytes = new byte[decodedToDecrypt.length - IV.length]; System.arraycopy(decodedToDecrypt, IV.length, encryptedBytes, 0, encryptedBytes.length); cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec, new SecureRandom()); cipher.updateAAD(aadTagData); cipher.update(encryptedBytes); byte[] decryptedBytes = cipher.doFinal(); If you have any comments or more information, kindly comment down in this post. You can refer to the entire code on my Github.\n[UPDATE] # Tag Mismatch Error While testing some of the passwords, I found that the above code would give an *AEADTagException *for the password\u0026rsquo;s with length greater than the block size.\nThis is because of the Cipher.update(bytes[]) method.\nReferring to the documentation, you would find that\n**Returns:**the new buffer with the result, or null if the underlying cipher is a block cipher and the input data is too short to result in a new block.\nSo, the fix would be to directly pass the bytes[] in cipher.doFinal() method of Cipher. And avoid the cipher.update() method.*** ***\nbyte[] encryptedBytes = cipher.doFinal(bytePassword); References # https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html* * https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/GCMParameterSpec.html* * https://www.javacodegeeks.com/2017/12/choosing-java-cryptographic-algorithms-part-2-single-key-symmetric-encryption.html* * https://en.wikipedia.org/wiki/Galois/Counter_Mode* * ***[UPDATE] ***Cipher.update(byte[]) Documentation Link Hope you like the content of the blog. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/aes-algorithm-with-gcm-mode-in-java/","section":"Posts","summary":"\u003cp\u003eIn this post we will encrypt and decrypt a given phrase using AES algorithm in GCM mode. This will be more on how to do it. I am not an expert on ciphers, but a basic way of how would you achieve the encryption/decryption will help in securing your applications.\u003c/p\u003e\n\u003ch3 id=\"aes-algorithm\" class=\"relative group\"\u003e\u003cstrong\u003eAES Algorithm\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#aes-algorithm\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cblockquote\u003e\n\u003cp\u003eAdvanced Encryption Standard(AES) is a symmetric, block cipher encryption/decryption algorithm.\u003c/p\u003e\n\u003c/blockquote\u003e","title":"AES Algorithm with GCM Mode in Java"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/encryption/","section":"Categories","summary":"","title":"Encryption"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/encryption/","section":"Tags","summary":"","title":"Encryption"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/leetcode/","section":"Categories","summary":"","title":"Leetcode"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/leetcode/","section":"Tags","summary":"","title":"Leetcode"},{"content":"In this post we will explore one of the best feature provided by Java 9, its importance in present day development and how it will be a key in maintaining multiple version of code.\nWhen Java 9 released in July, 2017, it was not a Long term Support(LTS), though it was having multiple features which would help Java in long run. Some of it which is now playing a big role are\nMulti-Release Jar functionality, Introduction of reactive streams, Private methods in Interface, Deprecations of JavaFX, which is finally removed in Java 11 and maintained as a different project out of JDK umbrella, Modularity, which is on same concept as Microservices. Since this release was not a Long Term Support (LTS), Oracle came up with another release Java 10 and soon a LTS version Java 11.\nAt the time of writing this post, Oracle is advertising Java 19 (non-LTS) which is released in 2022. GA is in September, 2022.\nImportance #Now lets say we have our project written in Java 7, which was upgraded to Java 8 and then to the next LTS version which is Java 11. To perform these upgrades, developers took a long time as it is not just JDK upgrade, but all dependencies need to be made compatible. If some dependencies are not working, then look for their latest release or other alternatives which again involves other code changes. So upgrading to the latest is not an easy task and that\u0026rsquo;s why the estimation is also big.\nSince the bytecode version varies with each release, developers have to maintain the functionality and if there is a big release jump, like upgrading directly from Java 8 to Java 17 (LTS), it will bring a lot of changes which also brings load of maintaining the dependencies changes as well.\nTo help with this issue, Java 9 introduced multi-release functionality, which helps in maintaining Java code of different versions. This way the files changed as part of upgrade process, can be kept in another directory which will be picked by complier based on the version of Java it is running.\nLets go through an example using maven as build tool to create a project which is originally written in Java 8 and then was upgraded to Java 11 (multi-release).\nExample #This will be a very simple example where we are maintaining two codebase for the files that need change and will have the respective methods present, such that if this class is run by an incompatible version of Java, it would throw errors.\nThe project structure will be like this:\nLets create our Main class, where we will create an instance of Worker class. Assuming this worker class is the code that will be changed in the upgrade process, we will maintain another directory for Java 11 Worker class file.\nMain Class #public static void main(String[] args) { Worker worker = new Worker(); if(!worker.isNameBlank()) System.out.println(worker.getName()+\u0026#34;---\u0026#34;+worker.getVersion()); else System.out.println(\u0026#34;Name is Blank\u0026#34;); } Java 8 Worker class #This class is written with Functional Interface annotation specific to Java 8. Also some print statements which will identify it is coming from Worker.java complied and executed with Java 8.\n@FunctionalInterface interface FunctionOne{ int addTwoNum(int a, int b); } public class Worker { private int version = 8; private String name = \u0026#34;Worker8\u0026#34;; public boolean isNameBlank(){ FunctionOne total = (a, b) -\u0026gt; a+b; System.out.println(\u0026#34;Sum of 10, 20 is =\u0026#34;+ total.addTwoNum(10, 20)); return this.name == null || this.name.trim().isEmpty(); } public String getName(){ return this.name; } public int getVersion(){ return this.version; } } Java 11 Worker Class #Notice that in this class, we have used isBlank() and stripLeading() methods introduced in Java 11.\npublic class Worker { private int version = 11; private String name = \u0026#34;Worker11\u0026#34;; public boolean isNameBlank(){ return this.name.isBlank(); } public String getName(){ return this.name.stripLeading(); } public int getVersion(){ return this.version; } } POM.xml #Since I am using Maven as the build tool, I will add the configurations which tells the complier while compiling/building which Java class to pick.\n1. Set the Source and Target Complier version\n\u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;11\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;11\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;/properties\u0026gt; 2. Add the maven-complier plugin, which will have two executions ways, one for Java 8 and another for Java11.\n3. Add execution for Java 8\n\u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;java8\u0026lt;/id\u0026gt; \u0026lt;goals\u0026gt;\u0026lt;goal\u0026gt;compile\u0026lt;/goal\u0026gt;\u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;source\u0026gt;1.8\u0026lt;/source\u0026gt; \u0026lt;target\u0026gt;1.8\u0026lt;/target\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; **4. Add execution for Java 11 **\nNotice here that we are specifically telling complier that the source class is present in which directory for Java 11, and also letting it know that Multi-Release output is set to True.\n\u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;java11\u0026lt;/id\u0026gt; \u0026lt;goals\u0026gt;\u0026lt;goal\u0026gt;compile\u0026lt;/goal\u0026gt;\u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;release\u0026gt;11\u0026lt;/release\u0026gt; \u0026lt;compileSourceRoots\u0026gt;${project.basedir}/src/main/java11\u0026lt;/compileSourceRoots\u0026gt; \u0026lt;multiReleaseOutput\u0026gt;true\u0026lt;/multiReleaseOutput\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; 5. Adding Maven-Jar Plugin to create a manifest file which will have the Main class to start the execution.\nOnce the pom.xml is properly set, do a maven clean install to build the project. This will create a target directory which will have the final jar file.\nNow when executing using Java 8, it will give following output:\nWhile executing using Java 11, it will give the following output\nIf you open the Jar file, you will find the following structure:\nNotice that the version specific code is present under the META-INF/versions directory.\nYou can refer to my Github Repository for the full code base and unzipped jar file as well.\nHope this helps others also. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/multi-release-functionality/","section":"Posts","summary":"\u003cp\u003eIn this post we will explore one of the best feature provided by Java 9, its importance in present day development and how it will be a key in maintaining multiple version of code.\u003c/p\u003e\n\u003cp\u003eWhen Java 9 released in July, 2017, it was not a Long term Support(LTS), though it was having multiple features which would help Java in long run. Some of it which is now playing a big role are\u003c/p\u003e","title":"Multi Release Functionality"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/multirelease/","section":"Tags","summary":"","title":"MultiRelease"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/6249579158f0bd10b2a57273/","section":"Categories","summary":"","title":"\"6249579158f0bd10b2a57273\""},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/681676055d80aaac0c9329be/","section":"Categories","summary":"","title":"\"681676055d80aaac0c9329be\"]"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/619c9d9d8da2c200166cb558/","section":"Categories","summary":"","title":"[\"619c9d9d8da2c200166cb558\""},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/%231481/","section":"Tags","summary":"","title":"#1481"},{"content":"In this post, we will look into one of the Leetcode problem #1481, where we have to find the least number of unique integers after removing k elements.\nProblem Statement: #Given an array of integers arr and an integer k. Find the least number of unique integers after removing exactly k elements. So, the problem statements says to remove k elements from the given array in such a way that we get the least number of unique integers. Lets say we have following array:\narr[] = {5, 5, 4}; k=1; In this case, we can remove only one element from the array. If we remove 5, we will get 4 remaining or if we remove 4, we will get two counts of 5 remaining. In either case, the count of unique integers is 1.\nLets take another example, which is not given in the Leetcode #1481 problem statement:\narr[] = {2, 1, 1, 3, 3, 3}; k=3; In the above example, we have to remove 3 elements from the array.\nNow, if we remove one count of each 2, 1, 3 we will get one counts of 1 and two counts of 3 remaining. Lets say, if we remove all three counts of 3, we will get one counts of 1 and two counts of 1 remaining. Lets say, if we remove all two counts of 1 and one counts of 2, we will get all three counts of 3 remaining.\nSo, in any scenario, the output will be 2, and so is the answer.\nLogic/Algorithm #First and the foremost thing to do is to have the occurrence count of each elements in the array.\nAfter this, we have to remove the elements with the least occurrence count and gradually decrease the value of k.\nIn order to get the elements with the least count, we will prepare a map and sort it based on the values, which is the occurrence count.\n**Code Snippet ** #Let look into the code:\nGet the occurrence count of each elements present in the array #Map\u0026lt;Integer, Integer\u0026gt; countMap = new HashMap\u0026lt;\u0026gt;(); for(int i: arr){ countMap.put(i, countMap.getOrDefault(i, 0)+1); } Sort the map based on the occurrence count #Comparator\u0026lt;Map.Entry\u0026lt;Integer, Integer\u0026gt;\u0026gt; sortByCount = Comparator.comparing(Map.Entry::getValue); Map\u0026lt;Integer, Integer\u0026gt; linkedCountMap = countMap.entrySet() .stream() .sorted(sortByCount) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -\u0026gt; e1, LinkedHashMap::new) ); The above code is using a Comparator to sort the ***Map ***based on its values. We will construct a ***LinkedHashMap ***which will contain the sorted ***Map ***based on value, which is the occurrence count.\n**Iterate and remove the items from the Sorted map based on the value of k. ** #If the occurrence count of the elements is more than k, then we will deduct the occurrence count value by k\nIterator\u0026lt;Map.Entry\u0026lt;Integer, Integer\u0026gt;\u0026gt; itr = linkedCountMap .entrySet() .iterator(); Map.Entry\u0026lt;Integer, Integer\u0026gt; nextEle; while(itr.hasNext() \u0026amp;\u0026amp; k\u0026gt;0){ nextEle = itr.next(); if(nextEle.getValue()\u0026gt;k){ linkedCountMap.put(nextEle.getKey(), nextEle.getValue()-k); break; } k = k - nextEle.getValue(); itr.remove(); } Return the size of the LinkedHashMap.\nreturn linkedCountMap.size(); The entire code base can be found on Github repository. Also, this code took 81ms and it consumed 55.7mb of memory to run all test cases on Leetcode.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/lc1481-find-least-number-of-unique-integers-after-k-removals/","section":"Posts","summary":"\u003cp\u003eIn this post, we will look into one of the \u003ca href=\"https://leetcode.com/problems/least-number-of-unique-integers-after-k-removals/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eLeetcode problem #1481\u003c/em\u003e\u003c/a\u003e, where we have to find the least number of unique integers after removing k elements.\u003c/p\u003e\n\u003ch3 id=\"problem-statement\" class=\"relative group\"\u003e\u003cstrong\u003eProblem Statement:\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem-statement\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eGiven an array of integers \u003cem\u003e\u003cstrong\u003earr\u003c/strong\u003e\u003c/em\u003e and an integer \u003cem\u003e\u003cstrong\u003ek\u003c/strong\u003e\u003c/em\u003e. Find the \u003cem\u003eleast number of unique integers\u003c/em\u003e after removing exactly k elements.\nSo, the problem statements says to remove k elements from the given array in such a way that we get the least number of unique integers. Lets say we have following array:\u003c/p\u003e","title":"#LC1481 - Find Least Number of Unique Integers After K Removals"},{"content":"Devops Intelligence\nToday, I successfully built and deployed a Model Context Protocol (MCP) server using Spring AI that exposes real DevOps infrastructure through intelligent tools. But the journey? Let\u0026rsquo;s just say it involved more debugging than coding.\nIn this post, I\u0026rsquo;ll walk you through:\nWhat we built (the DevOps Intelligence Platform) The tools we created (K8s, Prometheus, Logs, Deployments) Every challenge we faced (and how we solved them) Why Spring AI 2.0.0-M2 is the sweet spot (M3 has still in development and after hours of debugging, settled with M2) TL;DR: Built an MCP server with 5+ DevOps tools. Spent 6 hours debugging. Spring AI 2.0.0-M2 is your friend. No manual bean configuration needed if you know what to configure.\nWhat is MCP? #Model Context Protocol lets Claude (or any AI) directly access your tools through a standardized interface.\nInstead of:\nMe → \u0026#34;Claude, what\u0026#39;s my cluster health?\u0026#34; Claude → \u0026#34;I don\u0026#39;t know, you need to tell me\u0026#34; Now:\nMe → \u0026#34;Claude, what\u0026#39;s my cluster health?\u0026#34; Claude → *calls k8s_cluster_health tool* → Returns real data That\u0026rsquo;s the power of MCP. Your AI gets direct access to your infrastructure.\nThe DevOps Intelligence Platform: What We Built 🛠️ #We created a Spring Boot application that exposes real DevOps infrastructure as MCP tools:\nTools Implemented: #K8s Tools (KubernetesTools)\nk8s_cluster_health - Overall cluster status k8s_list_pods - List all pods with filtering k8s_recent_events - Recent cluster events Prometheus Tools (PrometheusTools)\nprometheus_query_cpu - Query CPU metrics prometheus_query_memory - Query memory metrics prometheus_alerts - Active alerts Logs Tools (LogsTools)\nlogs_search - Search application logs logs_errors - Get recent errors logs_by_service - Filter logs by service Deployment Tools (DeploymentTools)\ndeployment_history - Deployment history deployment_rollback - Rollback mechanism deployment_status - Current deployment status Here\u0026rsquo;s what the MCP Inspector shows when fully connected: MCP Inspector The DevOps Intelligence server with all tools discovered and ready to execute\nArchitecture: How It All Fits Together # Architecture Challenge #1: Tomcat Web Server Pollution #The Problem:\nmvn spring-boot:run 20:01:48.612 [main] INFO o.s.boot.tomcat.TomcatWebServer - Tomcat initialized with port 8080 20:01:48.616 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat] When you add spring-boot-starter-web, Spring Boot starts Tomcat HTTP server automatically. But MCP STDIO protocol needs pure process I/O, not HTTP.\nThe Solution: Remove spring-boot-starter-web from pom.xml:\n\u0026lt;!-- REMOVE THIS --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Keep only:\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.ai\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-ai-starter-mcp-server\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.0-M2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Lesson: MCP STDIO doesn\u0026rsquo;t need a web server. It communicates directly via stdin/stdout.\nChallenge #2: Process Exits Immediately ⚡💀 #The Problem:\nStarted DevopsIntelligenceApplication in 0.301 seconds [INFO] BUILD SUCCESS (process exits) Without Tomcat (or WebFlux), Spring Boot has nothing to keep the process alive.\nThe Solution:\nOption A: Add spring-boot-starter-webflux (for light reactive server):\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-webflux\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Option B: Keep process alive manually:\npublic static void main(String[] args) { SpringApplication.run( DevopsIntelligenceApplication.class, args); try { Thread.currentThread().join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } I went with Option A (WebFlux) because it\u0026rsquo;s the Spring Boot standard.\nLesson: Something must keep the Spring Boot process alive. Either a server component or explicit thread joining.\nChallenge #3: JSON Protocol Corruption #The Problem:\nSTDIO transport: command=...jar Error in /stdio route: SyntaxError: Unexpected non-whitespace character after JSON at position 2 Error from MCP server: SyntaxError: Unexpected token \u0026#39;.\u0026#39;, \u0026#34; . ____ \u0026#34;... is not valid JSON What was happening: Spring Boot was printing startup banners and logs to STDOUT, which corrupted the MCP protocol messages. MCP STDIO uses stdin/stdout exclusively.\nANY other output breaks the JSON-RPC protocol.\nThe Solution:\nConfigure application.yml to suppress all output:\nyaml\nspring: main: banner-mode: \u0026#34;off\u0026#34; # No ASCII art log-startup-info: false # No startup messages application: name: devops-intelligence-mcp ai: mcp: server: name: devops-intelligence version: 1.0.0 type: SYNC protocol: STDIO logging: level: root: OFF # Turn off ALL logging file: name: mcp-server.log # Send logs to file instead Key Settings:\nbanner-mode: \u0026ldquo;off\u0026rdquo; - No Spring Boot ASCII art log-startup-info: false - No startup logs logging.level.root: OFF - Suppress all logs file.name: mcp-server.log - Redirect logs to file Lesson: For STDIO protocol, STDOUT must be completely clean. Even whitespace or warning messages break it.\nChallenge #4: Permission Denied (EACCES) #The Problem:\nError: spawn /Users/.../target/devops-intelligence-0.0.1-SNAPSHOT.jar EACCES errno: -13 code: \u0026#39;EACCES\u0026#39; (permission denied) The Solution:\nchmod +x target/devops-intelligence-0.0.1-SNAPSHOT.jar BUT this still didn\u0026rsquo;t work because JAR files aren\u0026rsquo;t executable binaries.\nReal Solution: Use java -jar instead in MCP Inspector UI:\nCommand: java Arguments: -jar /full/path/to/target/devops-intelligence-0.0.1-SNAPSHOT.jar Lesson: JAR files need java -jar to execute, not direct execution.\nChallenge #5: Request Timeout (32001) #The Problem:\nMCPError: 32001: Request timed out Connection Error - Check if your MCP server is running and proxy token is correct Root Cause: The server was running, but the MCP Server bean wasn\u0026rsquo;t being initialized. The annotation scanner found tools, but there was no actual server to respond to protocol messages.\nThe \u0026ldquo;Fix\u0026rdquo; I Tried (Didn\u0026rsquo;t Work):\n@Bean public McpSyncServer mcpSyncServer() { return McpSyncServer.builder() // ← This doesn\u0026#39;t exist in M2! .name(\u0026#34;devops-intelligence\u0026#34;) .build(); } Error:\nThere is no builder with io.modelcontextprotocol.server.McpSyncServer The Real Fix: Upgrade to Spring AI 2.0.0-M2 (not M1 or M3)\nIn M2, the annotation scanner + auto-configuration handles everything:\nspring: ai: mcp: server: name: devops-intelligence version: 1.0.0 type: SYNC protocol: STDIO That\u0026rsquo;s it. No manual bean creation needed.\nLesson: Spring AI versions matter. M2 has the sweet spot. M1 is outdated, M3 has bugs.\nChallenge #6: Tools Not Discovered #The Problem: Server connected but tools tab was greyed out. No tools were visible.\nDiagnosis:\n[✗] MCP Server bean NOT found [✓] Found: org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration [✓] Found tool bean: k8sTools [✓] Found tool bean: prometheusTools Tools were registered as Spring beans, but the MCP Server wasn\u0026rsquo;t being created.\nThe Root Cause: In Spring AI 2.0.0-M2, the auto-configuration for MCP server isn\u0026rsquo;t fully automatic. The annotation scanner finds tools, but you need to ensure all components are properly wired.\nSolution 1 (Tried First - Overcomplicated): Created a custom McpServerConfiguration with manual tool registration. Didn\u0026rsquo;t work because the API wasn\u0026rsquo;t right.\nSolution 2 (What Actually Worked - SIMPLE): Just ensure:\n✅ @Component on tool classes\n✅ @McpTool on tool methods\n✅ Proper application.yml\n✅ Dependency: spring-ai-starter-mcp-server:2.0.0-M2\nSpring AI M2 handles the rest automatically.\nLesson: Less is more. Don\u0026rsquo;t overthink it. Spring AI M2 is designed to auto-discover tools if configured correctly.\nThe Final Solution: application.yml #Here\u0026rsquo;s the complete, minimal configuration that works:\nspring: main: web-application-type: none banner-mode: \u0026#34;off\u0026#34; # log-startup-info: false application: name: devops-intelligence-mcp ai: mcp: server: name: devops-intelligence version: 1.0.0 # type: SYNC # protocol: STDIO stdio: true logging: level: root: INFO file: name: mcp-server.log server: port: 8080 That\u0026rsquo;s it. Everything else is default.\nTool Implementation Example: K8sTools #Here\u0026rsquo;s how a tool class looks:\n@Component public class K8sTools { private final K8sMockClient k8sClient; public K8sTools(K8sMockClient k8sClient) { this.k8sClient = k8sClient; } @McpTool( name = \u0026#34;k8s_cluster_health\u0026#34;, description = \u0026#34;Get overall Kubernetes cluster health including node and pod status\u0026#34; ) public String describeClusterHealth() { var pods = k8sClient.listPods(); var nodes = k8sClient.listNodes(); long healthyPods = pods.stream() .filter(p -\u0026gt; \u0026#34;Running\u0026#34;.equals(p.status()) \u0026amp;\u0026amp; \u0026#34;1/1\u0026#34;.equals(p.ready())) .count(); long readyNodes = nodes.stream() .filter(n -\u0026gt; \u0026#34;Ready\u0026#34;.equals(n.status())) .count(); StringBuilder sb = new StringBuilder(); sb.append(\u0026#34;Cluster Health Summary:\\n\u0026#34;); sb.append(String.format(\u0026#34;Nodes: %d/%d Ready\\n\u0026#34;, readyNodes, nodes.size())); sb.append(String.format(\u0026#34;Pods: %d/%d Running\\n\u0026#34;, healthyPods, pods.size())); return sb.toString(); } @McpTool( name = \u0026#34;k8s_list_pods\u0026#34;, description = \u0026#34;List all Kubernetes pods with their status\u0026#34; ) public String listPods( @McpToolParam(description = \u0026#34;Filter by namespace (optional)\u0026#34;, required = false) String namespace) { var pods = k8sClient.listPods(); if (namespace != null \u0026amp;\u0026amp; !namespace.isEmpty()) { pods = pods.stream() .filter(p -\u0026gt; p.namespace().equals(namespace)) .collect(Collectors.toList()); } StringBuilder sb = new StringBuilder(\u0026#34;Kubernetes Pods:\\n\u0026#34;); pods.forEach(p -\u0026gt; sb.append(String.format( \u0026#34; %s/%s - Status: %s, Ready: %s\\n\u0026#34;, p.namespace(), p.name(), p.status(), p.ready() )) ); return sb.toString(); } } Key Points:\n@Component - Spring discovers this as a bean @McpTool - Spring AI discovers this as an MCP tool Tool methods return String (serialized to JSON-RPC) @McpToolParam - Optional parameters for tools Constructor injection for dependencies Since these are getting mock data, we can use the client libraries for Kuberentes, or ELK, or Github, to fetch data from the infrastructure you are going to connect this tool with.\nFor example using the Kubernetes client libraries\n/** * * Ref Source: https://github.com/kubernetes-client/java/blob/master/examples/ */ public interface KubernetesClient { static Class\u0026lt;? extends KubernetesObject\u0026gt; getClassForKind(String kind) { return switch (kind) { case \u0026#34;pod\u0026#34;, \u0026#34;pods\u0026#34; -\u0026gt; V1Pod.class; case \u0026#34;deployment\u0026#34;, \u0026#34;deployments\u0026#34; -\u0026gt; V1Deployment.class; case \u0026#34;service\u0026#34;, \u0026#34;services\u0026#34; -\u0026gt; V1Service.class; case \u0026#34;node\u0026#34;, \u0026#34;nodes\u0026#34; -\u0026gt; V1Node.class; case \u0026#34;replicationcontroller\u0026#34;, \u0026#34;replicationcontrollers\u0026#34; -\u0026gt; V1ReplicationController.class; default -\u0026gt; null; }; } String PADDING = \u0026#34; \u0026#34;; private static String pad(String value) { while (value.length() \u0026lt; PADDING.length()) { value += \u0026#34; \u0026#34;; } return value; } default KubernetesObject delete(String kind, String ns, String name) throws KubectlException { return Kubectl.delete(getClassForKind(kind)) .namespace(ns) .name(name) .execute(); } default KubernetesObject drain(ApiClient apiClient, String name) throws KubectlException, IOException { return Kubectl.drain() .apiClient(apiClient) .name(name) .execute(); } default void portForwarding(ApiClient apiClient, String ns, String name, String port) throws KubectlException { //port: 8080:8080 KubectlPortForward forward = Kubectl.portforward() .apiClient(apiClient) .name(name) .namespace(ns); String[] ports = port.split(\u0026#34;:\u0026#34;); System.out.println(\u0026#34;Forwarding \u0026#34; + ns + \u0026#34;/\u0026#34; + name + \u0026#34; \u0026#34; + ports[0] + \u0026#34;-\u0026gt;\u0026#34; + ports[1]); forward.ports(Integer.parseInt(ports[0]), Integer.parseInt(ports[1])); forward.execute(); } default void scale(ApiClient apiClient, String kind, String ns, String name, Integer replicas) throws KubectlException { Kubectl.scale(getClassForKind(kind)) .apiClient(apiClient) .namespace(ns) .name(name) .replicas(replicas) .execute(); } default String getVersion(ApiClient apiClient) throws KubectlException { return Kubectl.version() .apiClient(apiClient) .execute() .toString(); } default Integer execute(ApiClient apiClient, String ns, String name, String container, String[] command) throws KubectlException { return Kubectl.exec() .apiClient(apiClient) .namespace(ns) .name(name) .command(command) .container(container) .execute(); } . . . . } Refer to the repo for the complete code base. Similarly ELK and Github should be using their client libraries.\nTesting with MCP Inspector #Once connected, you can:\nSee all tools in the Tools tab Click a tool to view its parameters Execute the tool and see results in real-time Check History of all tool calls The Inspector shows exactly what Claude sees when using your tools.\nGitHub Repository #Full source code available: 🔗 https://github.com/ankitagrahari/devops-intelligence\nKey files:\nsrc/main/java/org/backendbrilliance/devopsintelligence/tools - All tool implementations src/main/resources/application.yml - Configuration src/main/java/org/backendbrilliance/devopsintelligence/clients/mocks - Mock clients src/main/java/org/backendbrilliance/devopsintelligence/clients - Actual client implementation. Lessons Learned 📚 # Spring AI 2.0.0-M2 is the sweet spot - M1 is old, M3 is not completely ready as it is still under snapshot view. MCP STDIO needs pure I/O - No logs, no web servers, no output to STDOUT Auto-configuration \u0026gt; Manual beans - Let Spring do the heavy lifting Annotation scanner is powerful - @Component + @McpTool is all you need Test with MCP Inspector first - Then integrate with Claude Desktop Mock clients for development - Real infrastructure comes later What\u0026rsquo;s Next? # ✅ MCP server running with 5+ tools ✅ Inspector can discover and execute tools ✅ Ready to integrate with Claude Desktop 🔜 Real K8s/Prometheus integration 🔜 Autonomous incident response 🔜 Cost optimization analysis Final Thoughts #Building an MCP server is surprisingly straightforward once you know the gotchas:\nRemove web servers (STDIO needs pure I/O) Suppress all logging (STDOUT is sacred) Use Spring AI 2.0.0-M2 (sweet spot version) Keep configuration minimal (auto-discovery works) The debugging journey was long, but the end result is clean, maintainable code that gives Claude direct access to your infrastructure. That\u0026rsquo;s powerful.\nNext stop: Making Claude your autonomous DevOps sidekick. 🚀\nStay Updated #Follow me for more:\n📝 Blog: dynamicallyblunttech.com 📷 Instagram: @backendbrilliance 💼 LinkedIn: ankitagrahari 💻 Medium: ankitagrahari.rkgit Happy building! #","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/building-devops-intelligence-using-mcp-server-with-spring-ai-tools-challenges-solutions/","section":"Posts","summary":"\u003cp\u003eDevops Intelligence\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n\t\t\t\u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube.com/embed/DLVG0z-aHV4?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n\t\t\u003c/div\u003e\n\n\u003cp\u003eToday, I successfully built and deployed a \u003cstrong\u003eModel Context Protocol (MCP) server\u003c/strong\u003e using Spring AI that exposes real DevOps infrastructure through intelligent tools. But the journey? Let\u0026rsquo;s just say it involved more debugging than coding.\u003c/p\u003e\n\u003cp\u003eIn this post, I\u0026rsquo;ll walk you through:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWhat we built (the DevOps Intelligence Platform)\u003c/li\u003e\n\u003cli\u003eThe tools we created (K8s, Prometheus, Logs, Deployments)\u003c/li\u003e\n\u003cli\u003eEvery challenge we faced (and how we solved them)\u003c/li\u003e\n\u003cli\u003eWhy Spring AI 2.0.0-M2 is the sweet spot (M3 has still in development and after hours of debugging, settled with M2)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR:\u003c/strong\u003e Built an MCP server with 5+ DevOps tools. Spent 6 hours debugging. Spring AI 2.0.0-M2 is your friend. No manual bean configuration needed if you know what to configure.\u003c/p\u003e","title":"Building DevOps Intelligence using MCP Server with Spring AI: Tools, Challenges \u0026 Solutions"},{"content":"In this post we will be creating a registration form which will include textbox, dropdown with multi-select option, file pickers and then bind it with backend REST services which can be further stored in some database or can be used for any other purpose.\nThymeleaf #As stated in official Thymeleaf website,\nThymeleaf is a modern server-side Java template engine for both web and standalone environments.\nSpring can be integrated with Thymeleaf to process or parse HTML, CSS, JS, XML, code. It becomes very simple to create an UI for your application in very little time. Thymeleaf can be used for modern day HTML5 web development as well.\nBased on the requirement of dependencies, if you want to start a project with Spring and Thymeleaf, thymeleaf can be selected as dependency with others to give the basic template to start with from https://start.spring.io/.\nRegistration Form #The form would look something like this: Your browser does not support the video tag. The elements are random textboxes to store either name, version, descriptions, return type or platform type etc.\nWe will be checking following:\nThe description section which is storing a nested class, where the default, detailed, audit, remediate description variables are part of nested class,\nThe Supported platform is a list of variables displayed as drop down where user can select multiple values.\nSimilarly Scripts, Evaluate and Remediate Arguments will be a dynamic table where user can add or remove row.\nAdding multiple file chooser support in the same form.\nCreate Spring Project from Spring Initializer #Dependencies #We will using lombok to have clean code without any of the boilerplate code and thymeleaf support.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-thymeleaf\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;/dependency\u0026gt; **Create Java Object for Registration Form ** #The POJO will have the elements that we want as part of the form like the ones displayed in the form. This POJO class will be binded with the form and the elements or attributes of the POJO can be referenced in the HTML form by their name, either by *{} or ${}.\nThe registration form will look like this:\n@Data public class Controls { private String name; private String version; private String guid; private Description description; private String returnType; private String platformType; private SupportedPlatforms[] platformSupported; private String[] imports; private List\u0026lt;Script\u0026gt; scripts; private MultipartFile ePyFile; private MultipartFile rPyFile; private List\u0026lt;EArgument\u0026gt; eArguments; private List\u0026lt;RArgument\u0026gt; rArguments } You will notice that using lombok eliminates developer to write all the getter, setter and the constructors. The annotation @Data takes care of these elements in the POJO.\nCreate Form #We will start by creating a form which will be calling the REST API to save controls.\nLets say the URL to save the registration form is /saveControls, then code to add the form will look like this:\n\u0026lt;form action=\u0026#34;#\u0026#34; th:action=\u0026#34;@{/saveControls}\u0026#34; th:object=\u0026#34;${controls}\u0026#34; method=\u0026#34;post\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34; \u0026gt; Here the ***action ***attribute will letting us know that the REST API can be called from this URL, which will be mapped to some method is some controller.\nSimilarly, object attribute of form is mapped to the Form object in java. In our case it is the POJO which we created above Controls.java.\nThe form will use the post method and pass this form as the payload and since the form is having files to be attached to the payload we will be using multipart/form-data as our encode type.\nCreate Textbox with Label #Lets take an example of the name field,\n\u0026lt;div class=\u0026#34;form-group\u0026#34; th:align=\u0026#34;left\u0026#34;\u0026gt; \u0026lt;label style=\u0026#34;font-weight: bolder\u0026#34; for=\u0026#34;name\u0026#34;\u0026gt;Name:\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; id=\u0026#34;name\u0026#34; th:field=\u0026#34;*{name}\u0026#34; required\u0026gt; \u0026lt;p class=\u0026#34;alert alert-danger\u0026#34; th:if=\u0026#34;${#fields.hasErrors(\u0026#39;name\u0026#39;)}\u0026#34; th:errors=\u0026#34;*{name}\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; The input label can be added using html tag and the textbox can be added using input tag where type is defined as text. In the input tag, notice that the field tag is added which has name variable used. This name variable is mapped to the POJO Controls.java attribute name.\nSimilarly, we can add other textbox and its label.\nUsing Nested Class to display field variable #There is a very high chance you will encounter this scenario where the Parent class is using child class which might be using another child class. Like in this example where Controls is using Description class which is mapped in the form using attribute defined in the Parent and accessing the attributes in the child by calling dot on the child attribute like this\n\u0026lt;label style=\u0026#34;font-weight: bold\u0026#34; for=\u0026#34;description.defaultDescription\u0026#34;\u0026gt;Default Description:\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; id=\u0026#34;defaultDescription\u0026#34; th:field=\u0026#34;*{description.defaultDescription}\u0026#34;\u0026gt; If you check the field attribute, it is calling the attribute defined in the parent class ***description ***and accessing the child attributes by calling description.defaullDescription.\n**Create Drop Down ** #The drop down requires a list of records which can be viewed in the drop down. The list can be dynamic or can be static which remains same. Like the Choose Platform Type variable, the number of records remains same and will be updated if RHEL introduce a new version or Windows comes up with a new version of it.\n\u0026lt;div class=\u0026#34;form-group\u0026#34; th:align=\u0026#34;left\u0026#34;\u0026gt; \u0026lt;label style=\u0026#34;font-weight: bolder\u0026#34; for=\u0026#34;platformSupported\u0026#34;\u0026gt;Choose Supported Platform\u0026lt;/label\u0026gt; \u0026lt;select class=\u0026#34;custom-select \u0026#34; th:field=\u0026#34;*{platformSupported}\u0026#34; id=\u0026#34;platformSupported\u0026#34; multiple required\u0026gt; \u0026lt;option value=\u0026#34;\u0026#34;\u0026gt;Nothing selected\u0026lt;/option\u0026gt; \u0026lt;option th:each=\u0026#34;platformSupported : ${T(com.dynamicallyblunt.tech.thymeleafform.beans.SupportedPlatforms).values()}\u0026#34; th:value=\u0026#34;${platformSupported}\u0026#34; th:text=\u0026#34;${platformSupported}\u0026#34;\u0026gt;platformSupported \u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; \u0026lt;/div\u0026gt; The select and option tag is used for the drop down. In this example we have used an ENUM to store the list of platforms. Also notice the multiple keyword used, which will allow the user to select multiple values. For storing multiple values, we are using an array of SupportedPlatforms POJO.\nThe enum can be defined like this for different platforms:\npublic enum SupportedPlatforms { Unix, RHEL5, RHEL6, RHEL7, Centos6, Centos7, Centos8, Windows, Win2008, Win2k2008, Win2012, Win2k2012 } Create File Picker #The HTML tag to choose file is same as text, which is input tag. The input tag will use the type as file. In our example we are using bootstrap 4 which will make the button looks fancier. You can refer to the bootstrap v4 examples here.\nIt will look like this\n\u0026lt;div class=\u0026#34;custom-file\u0026#34;\u0026gt; \u0026lt;label style=\u0026#34;font-weight: bold\u0026#34; for=\u0026#34;chooseFile\u0026#34; class=\u0026#34;custom-file-label\u0026#34;\u0026gt;Choose Code file\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;file\u0026#34; th:field=\u0026#34;*{epyFile}\u0026#34; class=\u0026#34;custom-file-input\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; Create Dynamic Table # In this case, we will be creating a table in which once the user clicks on Add Row, we will be adding a new row for user to type in new records. Also we will provide one remove button which will delete that entry when it is clicked by user.\nOn click of the AddRow, we will submit the form with the parameter add row, which will call a different method in the controller. The purpose of this method will be to add a new object to the Control\u0026rsquo;s Arguments attribute.\nThe html code will be like\n\u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label style=\u0026#34;font-weight: bolder\u0026#34; for=\u0026#34;eArguments\u0026#34;\u0026gt;EArguments\u0026lt;/label\u0026gt; \u0026lt;button class=\u0026#34;btn btn-info\u0026#34; th:nowrap=\u0026#34;true\u0026#34; th:align=\u0026#34;right\u0026#34; type=\u0026#34;submit\u0026#34; name=\u0026#34;addEArgs\u0026#34; id=\u0026#34;addEArgs\u0026#34;\u0026gt;Add EArguments\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div th:if=\u0026#34;*{eArguments}\u0026#34;\u0026gt; \u0026lt;table cellpadding=\u0026#34;5px 15px;\u0026#34;\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;Index\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;Type\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;Default Label\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;Default Description\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;Default Value\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; \u0026lt;tr th:each=\u0026#34;eval, stat: *{eArguments}\u0026#34;\u0026gt; \u0026lt;td th:text=\u0026#34;${stat.count}\u0026#34;\u0026gt;1\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; th:field=\u0026#34;*{eArguments[__${stat.index}__].type}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; th:field=\u0026#34;*{eArguments[__${stat.index}__].defaultLabel}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; th:field=\u0026#34;*{eArguments[__${stat.index}__].defaultDescription}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; th:field=\u0026#34;*{eArguments[__${stat.index}__].defaultValue}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;button class=\u0026#34;btn btn-info\u0026#34; type=\u0026#34;submit\u0026#34; name=\u0026#34;removeEArgs\u0026#34; id=\u0026#34;removeEArgs\u0026#34; th:value=\u0026#34;${stat.index}\u0026#34;\u0026gt;Remove\u0026lt;/button\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; Here in the html tag, we are iterating over the list of eArguments objects and also keeping the index of every element in the list. It will help us in the remove section.\nIn the above section you can see the elements are iterated like *{eArguments[${stat.index}].type} where the ${} is used instead of directly using ${}. This is because in thymeleaf, the template will pre process the variable ${}, so that the outer variable will get the index instead of getting the variable. So something like first the ${} is calculated and the value is kept into the outer variable, which will look like this *{eArguments[0}__].type}\nWe will create a new method in the controller which will get triggered when the Add Row is triggered and it will add a new empty row for user to key in their inputs.\n@PostMapping(value = \u0026#34;/saveControls\u0026#34;, params = {\u0026#34;addEArgs\u0026#34;}) public String addEArgument(Controls controls, BindingResult bindingResult){ if(null!=controls){ if(null==controls.getEArguments()){ List\u0026lt;EArgument\u0026gt; evArgList = new ArrayList\u0026lt;\u0026gt;(); evArgList.add(new EArgument()); controls.setEArguments(evArgList); } else { controls.getEArguments().add(new EArgument()); } } return \u0026#34;registration\u0026#34;; } Similarly for remove method, it will call the same API but with removeEArgs parameters like this:\n@PostMapping(value = \u0026#34;/saveControls\u0026#34;, params = {\u0026#34;removeEArgs\u0026#34;}) public String removeEArgument(Controls controls, BindingResult bindingResult, HttpServletRequest request){ controls.getEArguments().remove(Integer.parseInt(request.getParameter(\u0026#34;removeEArgs\u0026#34;))); return \u0026#34;registration\u0026#34;; } We are getting the index by calling the request.getParameter(String s) method. Since the entire eArgs is a list, on removal it will remove that index element from the list using remove() method\nUsing the same approach we can add any number of dynamic table where user can add and edit the rows or remove rows from the list.\nWe will conclude now on the thymeleaf, but from here, we can create any form and do the binding with the POJO for any number of elements. You can find the complete codebase of this tutorials in this Github reporsitory..\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\nReferences #https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.pdf\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/exploring-spring-thymeleaf/","section":"Posts","summary":"\u003cp\u003eIn this post we will be creating a registration form which will include textbox, dropdown with multi-select option, file pickers and then bind it with backend REST services which can be further stored in some database or can be used for any other purpose.\u003c/p\u003e\n\u003ch2 id=\"thymeleaf\" class=\"relative group\"\u003e\u003cstrong\u003eThymeleaf\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#thymeleaf\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eAs stated in official \u003ca href=\"https://www.thymeleaf.org/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eThymeleaf website\u003c/em\u003e\u003c/a\u003e,\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eThymeleaf\u003c/strong\u003e is a modern server-side Java template engine for both web and standalone environments.\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Exploring Spring Thymeleaf"},{"content":"To everyone\u0026rsquo;s surprise, the latest release of log4j 2.16.0 is now effected by Denial of Service vulnerability and is claimed to be fixed in another release 2.17.0.\nThis Denial of Service vulnerability is reported by Hideki Okamoto of Akamai technologies and other vulnerabilities researchers.\nVersions Impacted CVE Severity Type of Vulnerability Fixed in \u0026lt; 2.16.0 CVE-2021-45105 7.5 Denial of Service 2.17.0 \u0026lt; 2.15.0 CVE-2021-45046 9.0 (earlier 3.5) Remote Code Execution 2.16.0 \u0026lt; v2.14.0 CVE-2021-44228 10 Remote Code Execution 2.15.0 You can refer to the older vulnerabilities details and its mitigation steps in these post:\nCVE-2021-44228 CVE-2021-45046 Vulnerability - Uncontrolled Recursion from Self-Referential Lookups # When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, $${ctx:loginId}), attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup, resulting in a StackOverflowError that will terminate the process. This is also known as a DOS (Denial of Service) attack.\nMitigation #In order to fix this, it is suggested to upgrade the log4j version to 2.17.0\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.logging.log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.17.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; But there is also an alternative step provided also\nIn PatternLayout in the logging configuration, replace Context Lookups like ${ctx:loginId} or $${ctx:loginId} with Thread Context Map patterns (%X, %mdc, or %MDC). Otherwise, in the configuration, remove references to Context Lookups like ${ctx:loginId} or $${ctx:loginId} where they originate from sources external to the application such as HTTP headers or user input. So basically what it means is if there is a lookup in your log4j configuration file, then remove it in order to protect yourself from this vulnerability. The applications having these lookups will look something like this\n\u0026lt;File name=\u0026#34;Application\u0026#34; fileName=\u0026#34;application.log\u0026#34;\u0026gt; \u0026lt;PatternLayout\u0026gt; \u0026lt;pattern\u0026gt;%d %p %c{1.} [%t] $${ctx:loginId} %m%n\u0026lt;/pattern\u0026gt; \u0026lt;/PatternLayout\u0026gt; \u0026lt;/File\u0026gt; Also, it is suggested that instead of lookups like $${context.loginid} use Thread Context Map patterns. The Thread Context Map are introduced in log4j 2.x and are referred as ***Mapped Diagnostic Context, ***where as the Thread Context Stack is the Nested Diagnostic Context introduced in log4j 1.x.\nThe Stack and Map are managed per thread and are based on ThreadLocal by default.\nIt can configured using following parameters:\nSet the system property disableThreadContextMap to true to disable the Thread Context Map. Set the system property disableThreadContextStack to true to disable the Thread Context Stack. Set the system property disableThreadContext to true to disable both the Thread Context Map and Stack. Set the system property log4j2.isThreadContextMapInheritable to ***true ***to enable child threads to inherit the Thread Context Map. When the content is pushed to these map and stack, pattern layout also provide options to print the contents of the Thread Context Map and Stack using\n%X by itself to include the full contents of the Map. %X{key} to include the specified key. %x to include the full contents of the Stack. Also this vulnerability is only present in the log4j-core jar file and if your application is using only log4j-api, then it is not impacted.\nReferences # https://logging.apache.org/log4j/2.x/security.html* * https://logging.apache.org/log4j/2.x/manual/lookups.html* * https://logging.apache.org/log4j/2.x/manual/thread-context.html Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/log4j-vulnerability-saga-continues/","section":"Posts","summary":"\u003cp\u003eTo everyone\u0026rsquo;s surprise, the latest release of log4j 2.16.0 is now effected by Denial of Service vulnerability and is claimed to be fixed in another release 2.17.0.\u003c/p\u003e\n\u003cp\u003eThis Denial of Service vulnerability is reported by \u003cem\u003eHideki Okamoto\u003c/em\u003e of Akamai technologies and other vulnerabilities researchers.\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e\u003cstrong\u003eVersions Impacted\u003c/strong\u003e\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e\u003cstrong\u003eCVE\u003c/strong\u003e\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e\u003cstrong\u003eSeverity\u003c/strong\u003e\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e\u003cstrong\u003eType of Vulnerability\u003c/strong\u003e\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e\u003cstrong\u003eFixed in\u003c/strong\u003e\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u0026lt; 2.16.0\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eCVE-2021-45105\u003c/em\u003e\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e7.5\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eDenial of Service\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.17.0\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u0026lt; 2.15.0\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45046\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eCVE-2021-45046\u003c/em\u003e\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e9.0 (earlier 3.5)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eRemote Code Execution\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.16.0\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u0026lt; v2.14.0\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eCVE-2021-44228\u003c/em\u003e\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e10\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eRemote Code Execution\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.15.0\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eYou can refer to the older vulnerabilities details and its mitigation steps in these post:\u003c/p\u003e","title":"Log4j Vulnerability - Saga Continues "},{"content":"Multi-modal AI workflows are becoming essential for modern backend systems. They allow applications to process and generate different types of data—text, images, audio—within a unified framework. This capability opens new possibilities for richer user experiences and more intelligent automation. The MorphousAI project, built with Spring Boot, Vaadin, and Spring AI, offers a practical example of how to implement such workflows in a backend-first Java environment. This article explores MorphousAI’s architecture, technology choices, and key implementation details, providing insights for senior Java developers and architects interested in AI integration. Morphous AI Why Multi-Modal AI Matters for Backend Systems #AI models have traditionally focused on single data modalities, such as text or images. However, real-world applications often require handling multiple data types simultaneously. Multi-modal AI enables systems to understand and generate content across these modalities, such as converting text prompts into images or synthesizing speech from text.\nFor backend systems, supporting multi-modal AI means managing diverse data pipelines, integrating with AI services, and ensuring smooth communication between components. This complexity demands a robust architecture that can handle binary data (like images and audio), maintain statelessness for scalability, and secure sensitive API keys.\nMorphousAI addresses these challenges by demonstrating a backend-first approach that integrates multi-modal AI workflows using Java and Spring technologies. This approach is particularly relevant for Java developers who want to build AI-powered applications without switching to other languages or frameworks.\nSpring Boot \u0026amp; Vaadin - A Good Fit for AI-Driven Applications # Arch Spring Boot is a natural choice for backend development in Java due to its simplicity, extensive ecosystem, and support for microservices and cloud-native architectures. It provides a solid foundation for building stateless services that can scale horizontally, which is crucial when dealing with AI workloads that may require significant compute resources.\nVaadin complements Spring Boot by offering a server-driven UI framework that allows developers to build rich web interfaces entirely in Java. Unlike traditional frontend frameworks that require separate JavaScript codebases, Vaadin generates the frontend automatically from Java components. This integration reduces context switching and keeps the development experience consistent.\nIn MorphousAI, Vaadin handles the user interface for interacting with AI features, such as submitting text prompts, uploading images, and playing audio. The frontend directory is fully generated by Vaadin, meaning developers focus on Java code without manually writing HTML, CSS, or JavaScript. This setup aligns well with the backend-first philosophy and leverages Spring Boot’s strengths.\nThe Overall Architecture of MorphousAI #MorphousAI’s architecture centers around a Spring Boot backend that exposes AI services and a Vaadin-based UI layer. The backend integrates with OpenAI models through Spring AI, handling requests for text-to-image generation, text-to-speech synthesis, and image uploads.\nThe system is designed to be stateless, with each request processed independently. This design supports scalability and simplifies deployment in cloud environments. Binary data such as images and audio files are handled efficiently, with appropriate streaming and encoding mechanisms.\nThe UI components built with Vaadin communicate with backend services via Spring-managed beans. This tight integration allows the UI to remain reactive and server-driven, avoiding the complexity of client-side state management.\nSecurity is managed through environment-based configuration of API keys, ensuring sensitive credentials are not hardcoded. The architecture also supports extensibility, allowing new AI modalities or workflows to be added with minimal disruption.\nUsing Spring AI to Integrate with OpenAI Models #Spring AI provides a convenient abstraction layer for interacting with AI APIs, including OpenAI’s models. In MorphousAI, Spring AI manages authentication, request formatting, and response parsing, enabling developers to focus on business logic.\nThe integration involves defining service classes that invoke OpenAI endpoints for different AI tasks. For example, text-to-image generation uses the DALL·E model, while text-to-speech leverages OpenAI’s audio synthesis capabilities.\nSpring AI’s configuration supports environment variables for API keys, allowing secure and flexible deployment. The framework also handles error scenarios gracefully, providing fallback mechanisms or retries as needed.\nAI Service for Text-to-Image Generation #private final OpenAiImageModel imageModel; public MorphousTextToImageService(OpenAiImageModel imageModel) { this.imageModel = imageModel; } public Image textToImage(String textPrompt){ ImageOptions options = OpenAiImageOptions.builder() .model(\u0026#34;dall-e-3\u0026#34;) .width(1024) .height(1024) .style(\u0026#34;vivid\u0026#34;) .build(); ImagePrompt prompt = new ImagePrompt(textPrompt, options); ImageResponse response = imageModel.call(prompt); return response.getResult().getOutput(); } This service uses the OpenAI client to request an image based on a text prompt and then downloads the resulting image bytes for further processing or delivery.\nConfiguration Example (application.yml) #spring: application: name: MorphousAI ai: openai: api-key: ${OPENAI_API_KEY} chat: base-url: https://api.openai.com model: mistral vaadin: launch-browser: true allowed-packages: com.vaadin,org.vaadin,com.flowingcode,org.backendbrilliance.morphousai Using environment variables for API keys ensures that sensitive information is not stored in source code or configuration files directly.\nVaadin UI Components Calling Spring Services #Vaadin components in MorphousAI interact with backend services through Spring-managed beans. This approach keeps UI logic clean and leverages Spring’s dependency injection.\nHere is an example of a Vaadin view that allows users to enter a text prompt and display the generated image:\nprivate Component createTextToImage(){ VerticalLayout ttiLayout = new VerticalLayout(); TextArea textPrompt = new TextArea(); textPrompt.setMinLength(20); textPrompt.setManualValidation(true); textPrompt.setLabel(\u0026#34;Image generation prompt\u0026#34;); Button convert = new Button(\u0026#34;Convert\u0026#34;); convert.addClickListener(click -\u0026gt; { String prompt = textPrompt.getValue(); if(!textPrompt.isEmpty()){ //Call AI service to convert to image, and return the image. org.springframework.ai.image.Image image = textToImageService.textToImage(prompt); Image generatedImage = new Image(DownloadHandler.forFile(new File(image.getUrl())), \u0026#34;generated image\u0026#34;); ttiLayout.add(generatedImage); } else { textPrompt.setErrorMessage(\u0026#34;Image generation prompt cannot be empty!!\u0026#34;); } }); ttiLayout.add( textPrompt, convert ); return ttiLayout; } This component demonstrates how the UI remains simple and declarative while delegating AI logic to the backend service.\nUnderstanding Complex Concepts in MorphousAI #Multi-Modal AI #Multi-modal AI refers to systems that process and generate multiple types of data, such as text, images, and audio. MorphousAI supports workflows where a user can input text to generate images or audio, or upload images as inputs for further AI processing. This capability requires handling different data formats and integrating diverse AI models.\nText-to-Image Generation #Text-to-image generation converts descriptive text prompts into visual content. MorphousAI uses OpenAI’s DALL·E model for this task. The backend sends the prompt to the model, receives a URL to the generated image, and downloads the image bytes for display or further use.\nText-to-Speech Handling #Text-to-speech (TTS) converts text into spoken audio. MorphousAI integrates TTS by requesting audio data from OpenAI’s models, which return binary audio streams. The backend manages these streams, encoding them as base64 for transmission to the frontend, where Vaadin components play the audio.\nServer-Driven UI with Vaadin #Vaadin’s server-driven UI model means that UI components are defined and managed on the server side in Java. The framework automatically generates the frontend code, handling client-server communication transparently. This approach simplifies development by keeping all logic in one language and environment.\nBackend Concerns in MorphousAI #Stateless Services #MorphousAI’s services are stateless, meaning they do not store client-specific data between requests. This design supports scalability and fault tolerance, allowing multiple instances to handle requests independently.\nHandling Binary Data #The project handles binary data such as images and audio carefully. For example, image bytes are downloaded from URLs and encoded as base64 strings for embedding in the UI. Audio streams are similarly processed to enable playback in browsers.\nSecurity and API Key Management #API keys for OpenAI are managed through environment variables and injected into Spring configuration. This practice prevents accidental exposure of credentials and supports different environments (development, staging, production) without code changes.\nExtensibility and Scaling #MorphousAI’s modular design allows adding new AI modalities or workflows by creating new service classes and UI components. The stateless nature and Spring Boot’s support for containerization make scaling straightforward.\nThis could be good starting point to leverage Spring AI integration and can work on enhancing more on it. This will require some credits on the OpenAI, because these are paid services for image and audio generation. If we use Ollama, we don\u0026rsquo;t have a better model for image and audio generation, but explaining a image can be done using LLAVA model.\nFor Github Project link: https://github.com/ankitagrahari/MorphousAI?tab=readme-ov-file\nDo suggest topics, suggestion or your experience using Spring AI, Vaadin and OpenAI models.\nHappy learning :)\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/","section":"Posts","summary":"\u003cp\u003eMulti-modal AI workflows are becoming essential for modern backend systems. They allow applications to process and generate different types of data—text, images, audio—within a unified framework. This capability opens new possibilities for richer user experiences and more intelligent automation. The MorphousAI project, built with Spring Boot, Vaadin, and Spring AI, offers a practical example of how to implement such workflows in a backend-first Java environment. This article explores MorphousAI’s architecture, technology choices, and key implementation details, providing insights for senior Java developers and architects interested in AI integration.\n\n\n\n\n\n\n\u003cfigure\u003e\n    \n    \n\n\n\n\n\n\n\n\n  \n    \u003cpicture\n      class=\"mx-auto my-0 rounded-md\"\n      \n    \u003e\n      \n      \n      \n      \n        \u003csource\n          \n            srcset=\"https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_50e67ca2250094b2.webp 330w,https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_7d39b3273682b4ea.webp 660w\n            \n              ,https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_a1c06f18987d1ddd.webp 1024w\n            \n            \n              ,https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_53bc4e5a6a29a73b.webp 1320w\n            \"\n          \n          sizes=\"100vw\"\n          type=\"image/webp\"\n        /\u003e\n      \n      \u003cimg\n        width=\"1480\"\n        height=\"896\"\n        class=\"mx-auto my-0 rounded-md\"\n        alt=\"Morphous AI\"\n        loading=\"lazy\" decoding=\"async\"\n        \n          src=\"https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_c58dedb57041ef11.jpg\" srcset=\"https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_ebdc37301d0e8d8d.jpg 330w,https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_c58dedb57041ef11.jpg 660w\n          \n            ,https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_53832880814977d0.jpg 1024w\n          \n          \n            ,https://dynamicallyblunttech.com/posts/morphous-ai-building-multi-modal-ai-workflows-with-spring-boot-and-vaadin/images/morphous-ai_hu_72d6ddeb5b0032b6.jpg 1320w\n          \"\n          sizes=\"100vw\"\n        \n      /\u003e\n    \u003c/picture\u003e\n  \n\n\u003cfigcaption class=\"text-center\"\u003eMorphous AI\u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e","title":"Morphous AI: Building Multi-Modal AI Workflows with Spring Boot and Vaadin"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/vaadin/","section":"Tags","summary":"","title":"Vaadin"},{"content":"In this topic we will cover the leetcode #438 Find all anagram programming question.\nProblem Statement: #Given two strings s and p, return *an array of all the start indices of p\u0026rsquo;s anagrams in *s. You may return the answer in any order.\nInput #String from \u0026ndash; this is the main string from which we have to find all the anagrams.\nString find \u0026ndash; this is the string for which we have to find if its anagrams are present in the from string.\nAlgorithm/Logic #We will be using Sliding Window Algorithm. The logic here is to prepare an int array of 26 length (called hash), which contains the 0 as the initial value. It will increment value at those indexes, for which the character at the specific index in the find** **string is present.\npublic int[] prepareHash(String str){ int[] charCount = new int[26]; for(char c : str.toCharArray()){ charCount[c - \u0026#39;a\u0026#39;]++; } return charCount; } The ***find ***string length defines the length of the window. We will keep the ***left ***and ***right ***variable to keep the windows start and end indexes.\nint[] findHash = prepareHash(find); int left=0, right=find.length(); Now, for the characters present in the window, we will find the hash again and compare this int array with the hash we computed from the find** **string. If both the arrays are equal, then we will add the ***left ***value in the ***result ***list. If both the arrays are not equal, we will slide the window one step ahead, means we will increment the ***left ***and ***right ***variable values.\nwhile(right \u0026lt;= from.length()){ String temp = from.substring(left, right); int[] tempCharCount = prepareHash(temp); if(Arrays.equals(findHash, tempCharCount)){ result.add(left); } left++; right++; } Sliding the window, we will get all the possible anagrams present in the input from string. The below code will list all the indexes where we can find the anagrams.\nSystem.out.println(result.stream().map(Object::toString).collect(Collectors.joining(\u0026#34;,\u0026#34;))); You can find the entire code base here. Github Link\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/find-all-anagrams-sliding-window-algorithm/","section":"Posts","summary":"\u003cp\u003eIn this topic we will cover the \u003ca href=\"https://leetcode.com/problems/find-all-anagrams-in-a-string/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eleetcode #438\u003c/em\u003e\u003c/a\u003e Find all anagram programming question.\u003c/p\u003e\n\u003ch3 id=\"problem-statement\" class=\"relative group\"\u003eProblem Statement: \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem-statement\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eGiven two strings s and p, return *an array of all the start indices of p\u0026rsquo;s anagrams in *s. You may return the answer in \u003cstrong\u003eany order\u003c/strong\u003e.\u003c/p\u003e\n\u003ch3 id=\"input\" class=\"relative group\"\u003eInput \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#input\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003e\u003cem\u003e\u003cstrong\u003eString from\u003c/strong\u003e\u003c/em\u003e   \u0026ndash; this is the main string from which we have to find all the anagrams.\u003c/p\u003e","title":"#LC438 - Find All Anagrams - Sliding Window Algorithm"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/algorithm/","section":"Tags","summary":"","title":"Algorithm"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/slidingwindow/","section":"Categories","summary":"","title":"SlidingWindow"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/slidingwindow/","section":"Tags","summary":"","title":"SlidingWindow"},{"content":"Objective #As part of this blog post, we will be creating a simple Rest API using Spring AI to send and receive responses from Ollama on your local machine.\nWhy Ollama: #\u0026ndash; Ollama is an open-source tool enabling you to run LLMs such as deepseek, llama, mistral, chatgpt, and others on your local machine. This ensures that your data remains within your home network.\n\u0026ndash; The best part? There\u0026rsquo;s no need to generate multiple API keys and no limit.\n**Installing \u0026amp; Running Ollama ** #The Ollama includes an executable that can be obtained from https://ollama.com/download\nOnce downloaded, simply double-click to install it, and within a minute, it will be ready to execute commands on your local machine. You can then download and run various versions of models, enabling you to test all the latest updates available. Keep in mind that this will require more storage space.\nYou can also use docker to run Ollama. https://ollama.com/blog/ollama-is-now-available-as-an-official-docker-image. The mac users need to check if they want to run standalone or viai docker desktop, as Docker Desktop doesn\u0026rsquo;t support GPU.\nOn the Ollama terminal, you can use /? command or just write ollama to get the list of commands that can be executed. Ollama Command line To run our project, we need the ollama server running. You can either run any model of your choice. I am running the llama 3 (while writing this, llama3.3 is also available)\nollama run llama3.2 This will run the server where you can send any queries based on the prompt provided.\nURL to connect to your Ollama server running on your local is http://localhost:11434/api/generate\nProject Setup #We will navigate to spring intializer to setup the project and add the required dependencies. It should look like this Spring Initializr Notice the Ollama dependency added.\nLets now look into the application.properties file where you will define the Ollama specific parameters for your project to access the Ollama server and talk to it.\nspring.ai.ollama.base-url=http://localhost:11434 It defines the url where the local server is hosted.\nspring.ai.ollama.chat.options.model=llama3.2 It defines the model that shoulld be used.\nspring.ai.ollama.init.pull-model=when-missing Spring AI allows you to download the model as mentioned in this parameter. The possible values are\nalways: It will always download the model, even if it is present. This ensures that you are working with the latest version of model when_missing: It will download the model when it is not present. never: It will never download the model, unless and until it is explicitly done. There are a bunch of parameters which can be configured before running the application. You can find the details here Chat Properties\nHere is the sample of the application properties I used application.properties Lets now create a controller where we can define the API details to connect to ollama.\nJust before jumping to the controller, lets look into how the LLM model will understand what the user is asking. In order to do so, there are Prompt generated by user, which specifically tells the model about the details like what is expected as response, what could be the size of the response, what should be the main details in the response, etc.\nAnd Spring AI provides the ChatClient which offers a fluent API to communicate with AI models. It supports both asynchronous and synchronous programming model. It constructs the required part of the Prompt which is then fed to AI model.\nNow lets move to creating the Rest Controller, where we default ChatClient will be created which will process the user input to the AI model, and retrieve the response as per the request.\n@RestController public class ClientController { private final ChatClient client; private static final String MESSAGE_FORMAT = \u0026#34;\u0026#34;\u0026#34; { \u0026#34;model\u0026#34;: \u0026#34;llama3.2\u0026#34;, \u0026#34;messages\u0026#34;: [{\\s \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;,\\s \u0026#34;content\u0026#34;: \u0026#34;$message$\u0026#34;\\s } ] } \\s\u0026#34;\u0026#34;\u0026#34;; public ClientController(ChatClient.Builder builder) { this.client = builder.build(); } @GetMapping(\u0026#34;/ai/{message}\u0026#34;) Flux\u0026lt;ChatResponse\u0026gt; generateResponse(@PathVariable(value = \u0026#34;message\u0026#34;) String userInput){ userInput = MESSAGE_FORMAT.replace(\u0026#34;$message$\u0026#34;, userInput); return this.client.prompt(userInput) .stream() .content(); } } To create the default ChatClient, we can use the builder. But if you want to create with a specific requirement, then you can refer to this Spring AI documentation.\nOne thing to notice here is, we are using reactive web project and thats why the ChatClient is called with stream() rather than call() method.\nAnd you are all set to start your first Spring AI project which will connect to the Ollama server running locally.\nWhile starting the spring application, if the pull-model parameter is given as always, you will see that it will start downloading the specific model. This may take a lot of time depending on the size of the model and your internet speed. It is advisable if your are running locally to go with when_missing option instead of always.\nThe response to the API will load the response character by character as the streams of string is loaded.\nThis is just the basic to get the response from the Ollama server running locally. There are a bunch of updates which will comes on top of this in coming days. The way we are getting the input can also be improved. This is the github link for this project Spring AI with Ollama.\nThis post is after a long time, and I hope it will help you learning Spring AI.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/spring-ai-with-ollama/","section":"Posts","summary":"\u003ch4 id=\"objective\" class=\"relative group\"\u003e\u003cstrong\u003eObjective\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#objective\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003cp\u003eAs part of this blog post, we will be creating a simple Rest API using Spring AI to send and receive responses from Ollama on your local machine.\u003c/p\u003e\n\u003ch4 id=\"why-ollama\" class=\"relative group\"\u003e\u003cstrong\u003eWhy Ollama:\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#why-ollama\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003cp\u003e\u0026ndash; Ollama is an open-source tool enabling you to run LLMs such as deepseek, llama, mistral, chatgpt, and others on your local machine. This ensures that your data remains within your home network.\u003c/p\u003e","title":"Spring AI with Ollama "},{"content":"Agentic AI fails less often because of the model and more often because the system around the model was vague. The agent was given too much freedom, too little context, weak tools, no memory strategy, and no clear way to recover when things went wrong.\nA good agent is not a chatbot with extra prompts. It is a system that can plan, call tools, inspect results, revise its path, and stop at the right time. That means system design matters. The planner, tools, skills, hooks, memory, retrieval, guardrails, and evaluation loop all shape whether the agent behaves like a careful assistant or a noisy automation script.\nThis article looks at the design choices that matter most when building agentic AI systems. It also explains when Retrieval-Augmented Generation, or RAG, helps and when it adds cost or confusion without improving the outcome.\nAgentic system design starts with the work, not the model #The first design question is not “Which model should we use?” It is “What work must the system complete, and how much judgement does each step require?”\nAgentic AI fits tasks where the path is not fixed in advance. If every step is known, a normal workflow engine or deterministic service may be safer and cheaper. Agents earn their place when the system must interpret a goal, choose among tools, react to feedback, and handle variation.\nGood use cases include:\nInvestigating customer support issues across logs, tickets, and policies Drafting technical migration plans from codebase signals and internal standards Analysing procurement documents and asking for missing information Monitoring data quality issues and proposing fixes Assisting developers by reading files, running tests, and editing code under review Weak use cases include:\nSimple form filling Fixed approval routing Tasks that need exact arithmetic without a verified calculation tool High-risk decisions with no human review Workflows where latency and cost must be tightly bounded The core pattern is simple. Give the agent judgement where judgement is useful. Use deterministic systems where precision, speed, and repeatability matter more than flexibility.\nA useful planning exercise is to split the work into four zones.\nZone Best handled by Example Fixed logic Code or workflow engine Validate a GSTIN format before submission Knowledge lookup Search, RAG, or database query Find the latest internal leave policy Judgement LLM reasoning with constraints Decide whether a customer complaint is urgent Action Tool call with confirmation rules Create a ticket or send a draft response This split prevents a common mistake: asking the language model to do everything.\nThe model should reason and communicate.\nTools should calculate, fetch, write, verify, and execute.\nDefine the agent’s operating contract (Harness) #An agent needs a contract before it needs more tools. The contract states what the agent is allowed to do, what success looks like, and how it should behave when it is uncertain.\nA strong operating contract includes:\nThe agent’s goal The expected inputs and outputs Allowed tools and data sources Actions that need approval Stopping rules Error handling rules Privacy and security limits Evaluation criteria For example, a support triage agent might have this contract:\nClassify incoming support tickets, identify likely cause, attach relevant policy or product references, suggest the next best action, and escalate tickets that involve payments, security, legal risk, or unclear customer intent.\nThis contract does not just describe the task. It sets limits. The agent can classify and suggest. It cannot issue refunds or make legal interpretations. It knows which cases need escalation.\nUse roles as design boundaries, not theatrical prompts #Many agent prompts start with “You are an expert…” That can help tone, but it does not create safe behaviour by itself. The role should map to actual system boundaries.\nA useful role definition says:\nWhat the agent owns What the agent must not own Which tools it can call Which evidence it must cite Which cases it must hand off For example, a finance document agent can extract fields, compare invoice terms to purchase orders, and flag differences. It should not approve payment unless the approval system has explicit business rules and human sign-off.\nKeep the plan visible #If the agent creates a plan, save it. If it changes the plan, save the reason. This makes failures easier to debug and improves user trust.\nA plan record might include:\nGoal Assumptions Chosen steps Tool calls Observations Decision points Final result Confidence or uncertainty notes This record is useful for audit, testing, and product improvement. It also helps identify whether the agent made a poor plan, used the wrong tool, received bad data, or misunderstood the request.\nSkills turn broad agents into reliable specialists #A skill is a reusable capability with a clear purpose, inputs, outputs, and constraints. Instead of asking one agent to “handle procurement,” define smaller skills such as vendor comparison, contract clause extraction, compliance checklist generation, and exception detection.\nSkills make agentic systems easier to test. They also reduce prompt sprawl because each skill can carry its own instructions and examples.\nA good skill has five parts.\nSkill element What it means Example Trigger When the skill should be used Use when a document contains vendor payment terms Inputs The required data Contract text, vendor name, policy version Process The reasoning or tool steps Extract clause, compare with policy, flag mismatch Output The structured result Clause summary, risk level, evidence Guardrails Limits and escalation rules Escalate if clause is missing or ambiguous Design skills around frequent decisions\nDo not create skills for every tiny action. Create them around decisions that repeat and need consistency.\nFor example, a software maintenance agent may need these skills:\nRepository scan\nRead project structure, package files, and test commands.\nIssue reproduction\nRun relevant tests or scripts in a controlled environment.\nPatch proposal\nSuggest a minimal code change and explain the trade-offs.\nRegression check\nRun the test suite and summarise failures.\nPull request draft\nPrepare a human-readable change summary with affected files.\nEach skill can be tested with sample repositories. Each can fail safely. The patch proposal skill should not merge code. The regression check skill should not hide failed tests. The pull request draft skill should not claim success if tests were skipped.\nUse structured outputs where possible #Free-form text is pleasant for users, but structured outputs are better for systems. If another component needs the agent’s result, ask for JSON or a typed schema.\nFor a triage agent, a structured output could include:\n{ \u0026#34;category\u0026#34;: \u0026#34;billing\u0026#34;, \u0026#34;urgency\u0026#34;: \u0026#34;high\u0026#34;, \u0026#34;needs_human_review\u0026#34;: true, \u0026#34;evidence\u0026#34;: [ \u0026#34;Customer reports duplicate debit\u0026#34;, \u0026#34;Transaction reference included\u0026#34; ], \u0026#34;suggested_next_step\u0026#34;: \u0026#34;Route to payments support queue\u0026#34; } This does three things. It makes downstream routing easier. It makes testing easier. It reduces the risk that a useful detail gets buried in a paragraph.\nSkills should be small enough to test and useful enough to reuse Hooks let the system guide the agent at the right moments #Hooks are event-driven interventions in the agent loop. They run before or after key actions, such as a tool call, retrieval step, plan update, or final response. They help enforce policy, add context, check quality, and route control.\nThink of hooks as system-level checkpoints. The model can reason, but hooks keep the process safe and consistent.\nCommon hook points include:\nBefore planning After planning Before a tool call After a tool result Before retrieval After retrieval Before user-facing output After task completion Pre-planning hooks set the ground rules #A pre-planning hook can enrich the task with user permissions, tenant settings, feature flags, locale, and risk category. It can also reject tasks that should not reach the agent.\nFor example, a loan support assistant in India may receive a customer query about repayment charges. A pre-planning hook can attach the customer’s product type, language preference, and current policy version. It can also block the agent from giving personalised financial advice beyond approved support content.\nTool hooks prevent costly or unsafe actions #A before-tool hook can check whether a proposed tool call is allowed. An after-tool hook can validate the result.\nFor example, before a ticketing agent creates a Jira issue, a hook can check:\nDoes the user have permission? Is the project key valid? Is the title too vague? Does a similar issue already exist? Does this action need user confirmation? After the tool call, another hook can verify the issue ID and store the event.\nThis pattern matters because agents may call tools based on partial understanding. Hooks give the system a second layer of control.\nOutput hooks improve final quality #Before sending a final answer, an output hook can check whether the response:\nCites required evidence Avoids restricted claims Includes uncertainty where needed Uses the right language and format Matches the requested schema Does not expose sensitive data This is especially useful when agents combine internal data, retrieved documents, and generated reasoning. The final answer is the point where errors become user-visible.\nMemory needs design, not accumulation #Many agent projects treat memory as a bin where everything gets stored. That creates noise, privacy risk, and strange future behaviour.\nMemory should answer a design question: What should the agent remember to do future work better?\nThere are several memory types.\nMemory type Purpose Example Session memory Keep context during one task User already said the invoice period is April 2025 User preference memory Carry stable preferences User wants summaries in bullet points Task memory Track progress Three files were already checked Domain memory Store reusable knowledge Internal escalation policy Episodic memory Learn from past cases Similar error appeared after package upgrade Each type needs different retention rules. Session memory can expire quickly. Domain memory needs governance and versioning. User preference memory should be editable. Episodic memory should be curated because bad memories can pollute future work.\nDo not confuse memory with truth #Stored memory is not automatically correct. A user may state something wrong. A past task may have reached a poor result. An old policy may no longer apply.\nDesign memory with freshness and evidence. Attach dates, source references, and confidence where needed. Give the agent rules for when to trust memory and when to verify.\nFor example, a travel policy assistant should not rely on a remembered meal allowance from last year. It should check the current policy source before answering.\nRAG helps when the agent needs external knowledge #Retrieval-Augmented Generation connects a model to external documents or data. The system retrieves relevant content, gives it to the model, and asks the model to answer using that context.\nRAG is valuable when the answer depends on knowledge that is too large, too private, too new, or too changeable to live in the model’s training data.\nGood RAG scenarios include:\nInternal policy question answering Product support over changing documentation Legal or compliance research with citation needs Technical support using logs, tickets, and runbooks Knowledge assistants for large document repositories Agents that need evidence before taking action For example, an HR assistant for a nationwide Indian company may need to answer questions about leave, probation, reimbursements, and regional holidays. The model should not guess. It should retrieve the current HR policy, cite the relevant section, and respond in plain language.\nRAG also helps when the agent must show evidence. A generated answer without sources may sound confident but be hard to trust. Retrieved passages give users and reviewers something to inspect.\nUse RAG as part of the agent loop #In agentic systems, retrieval should not be a one-time step before generation. The agent may need to search, inspect, refine the query, and search again.\nA common pattern is:\nInterpret the user’s goal.\nDecide what knowledge is needed.\nRetrieve documents.\nInspect whether the documents are sufficient.\nAsk a narrower follow-up query if needed.\nAnswer with citations or take the next tool action.\nA procurement agent reviewing a vendor agreement may first retrieve the contract policy. Then it may retrieve approved clause examples. Then it may retrieve past exception decisions. Each retrieval has a purpose.\nRAG is not useful for every agent #RAG can make a system slower, more expensive, and harder to debug. It can also introduce irrelevant context that distracts the model.\nUse RAG when the task needs grounded knowledge. Skip it when the task mainly needs reasoning over information already in the prompt, a direct database lookup, or tool execution.\nRAG may not be beneficial in these cases.\nThe answer should come from a structured system #If the agent needs an order status, account balance, inventory count, or booking state, use an API or database query. Do not retrieve a document that describes where the data might be.\nA customer asking “Where is my order?” needs live order data. RAG over shipping policy pages may explain delivery timelines, but it cannot replace the order management system.\nThe knowledge is small and stable #If the task needs a short, stable rule set, place it directly in the system prompt or configuration. RAG adds needless retrieval risk.\nFor example, if an internal bot must always format dates as DD MMM YYYY and amounts in Indian numbering style, this rule can live in the prompt or output formatter.\nThe task needs exact computation #RAG does not calculate. If the agent needs to compute tax, compare large tables, or validate totals, use a calculator, code execution, or a verified business service. Retrieved documents may explain the formula, but a tool should perform the calculation.\nThe corpus is low quality #RAG only helps when the source content is useful. If documents are outdated, duplicated, contradictory, or poorly chunked, retrieval may return convincing nonsense. Fix the corpus before adding more agent logic.\nBad RAG often looks like a model problem. The real issue is source quality.\nThe user needs a creative or strategic draft #If a user asks for a brainstorming list, a rewrite, or a high-level planning draft based on information in the prompt, RAG may not add value. It can even make the output stiff by forcing irrelevant retrieved text into the response.\nBuild a retrieval strategy before choosing a vector database #Many teams start RAG design by picking a vector store. That is backward. Start with the retrieval behaviour the agent needs.\nKey decisions include:\nWhich sources are trusted? How often do they change? What metadata matters? How should documents be chunked? Should retrieval use keyword search, vector search, or both? How many passages should be returned? Should the agent rerank results? How should citations appear? What should happen when retrieval finds nothing useful? Good chunking reflects the task #Chunking is not just splitting documents every few hundred tokens. The chunk should match how people use the information.\nFor policies, chunk by clause or section. For API docs, chunk by endpoint. For troubleshooting guides, chunk by symptom and fix. For legal documents, preserve section numbers and definitions.\nA chunk should be large enough to contain meaning but small enough to avoid burying the relevant line.\nMetadata improves retrieval quality #Metadata helps the system filter before ranking. Useful metadata may include:\nDocument type Department Product version Region Effective date Access level Owner Language Status For a nationwide support system, region and policy version often matter. An agent answering a payroll question should not mix old and new policy versions, or confuse one state-specific rule with another.\nRetrieval needs failure behaviour #The agent should know what to do when retrieval fails. It should not invent.\nGood failure responses include:\nAsk a clarifying question Say the relevant source was not found Route to a human reviewer Use a safe general response with clear limits Trigger a document quality issue for the content team Failure behaviour is a product decision, not just a model setting.\nPlanning needs control over depth and cost #Agentic systems can overthink. A task that needs one search becomes six tool calls, three reflections, and a costly final answer. A task that needs careful analysis gets a shallow response.\nDesign the planning loop with limits and escalation.\nUseful controls include:\nMaximum number of steps Maximum tool calls by tool type Time budget Cost budget Required evidence threshold Confidence threshold Human review triggers Retry limits Stop conditions For example, a log analysis agent could follow this rule:\nIf the agent cannot identify a likely cause after three relevant log queries and one runbook search, it must summarise findings and escalate.\nThis stops the agent from wandering. It also gives engineers a clear failure packet.\nUse different planning modes #Not every task needs the same agent loop. Three modes work well in practice.\nMode Use when Behaviour Fast path The task is simple and low risk Answer or act with minimal tool use Standard path The task needs evidence or several steps Plan, retrieve, use tools, verify Careful path The task is high impact or ambiguous Add review, stronger checks, and stricter citations A support bot might use the fast path for “How do I reset my password?” It should use the careful path for “I was charged twice and need my money back.”\nEvaluation should test behaviour, not just answers #Agentic AI evaluation must cover the full loop. A correct final answer can hide a poor process. A poor final answer may come from a good plan with bad retrieval.\nEvaluate these layers separately:\nIntent understanding Plan quality Tool selection Tool argument correctness Retrieval relevance Evidence use Safety and policy compliance Final answer quality Cost and latency Recovery from failure Create test cases from real tasks, anonymised where needed. Include normal cases, edge cases, missing data cases, permission failures, and conflicting document cases.\nTrack traces for every run #A trace is the agent’s execution record. It should show prompts, tool calls, retrieved chunks, intermediate decisions, hook outcomes, and final output.\nTraces help answer practical questions:\nDid the agent search the right source? Did retrieval return the right passage? Did the model ignore the evidence? Did a hook block the right action? Did the agent stop too early? Did it ask for human review when needed? Without traces, debugging becomes guesswork.\nUse human review where judgement is expensive #Some tasks should not be fully automated. Human review is part of good system design, not a sign of failure.\nReview is useful when:\nThe action has financial, legal, or safety impact The agent’s confidence is low Source documents conflict The user disputes the result The task falls outside known patterns The agent proposes an irreversible action A well-designed system routes only the right cases to humans. It should include the agent’s evidence, plan, and uncertainty so the reviewer does not start from zero.\nA practical design pattern for an agentic system #A strong agentic architecture often has these components:\nTask intake\nParse the user goal, collect required fields, and assign risk level.\nContext builder\nAdd user permissions, tenant settings, locale, session history, and relevant memory.\nPlanner\nDecide the steps, tools, and retrieval needs.\nSkill router\nSelect reusable skills instead of one giant prompt.\nRetrieval layer\nSearch trusted knowledge sources when needed.\nTool layer\nCall APIs, databases, code runners, ticketing systems, or calculators.\nHook layer\nEnforce policy before and after key actions.\nVerifier\nCheck evidence, output format, and task completion.\nHuman review\nRoute risky or unclear cases with full trace context.\nLearning loop\nUse traces, feedback, and test failures to improve skills, hooks, retrieval, and prompts.\nHere is a practical example.\nA field service agent receives this request:\n“Create a plan to fix repeated inverter shutdowns at Site A. Check past incidents, current device logs, and maintenance notes.”\nThe system could work like this:\nIntake classifies the task as technical diagnosis. Context builder adds site access permissions and device type. Planner decides it needs incident history, logs, and maintenance records. Retrieval gets maintenance notes and runbooks. Tool calls fetch recent telemetry and event logs. A diagnostic skill compares symptoms with known failure patterns. A hook blocks the agent from scheduling physical work without approval. The verifier checks whether the plan cites evidence. The final response gives likely causes, recommended checks, required parts, and open uncertainties. Human review is triggered if the recommendation includes safety-sensitive electrical work. This is agentic AI used well. The model reasons across messy inputs. Tools fetch live facts. RAG grounds the answer in trusted documents. Hooks prevent unsafe action. Review catches high-impact cases.\nTips for building agents that behave well in production #Start narrow. Choose a task with clear value, visible outcomes, and examples of past work. Avoid starting with a broad “AI assistant for everything.”\nWrite down the agent contract before writing prompts. If the boundaries are unclear in design, they will be unclear in production.\nPrefer small skills over one large prompt. Skills can be tested, reused, and improved without changing the whole system.\nGive tools strict schemas. Validate tool arguments before execution. Treat every tool call as a system boundary.\nUse RAG only when the answer needs grounded knowledge. Build source quality, metadata, and failure handling before worrying about model tricks.\nAdd hooks early. Retrofitting policy checks after the agent has grown usually creates brittle patches.\nLog traces from day one. The first production issue will need them.\nMeasure cost and latency by task type. A careful agent is useful only if it fits the product experience and budget.\nKeep humans in the loop for high-risk work. The best systems combine machine speed with human responsibility.\nThe takeaway #Planning system design with Agentic AI Skills Hooks and Smart RAG Use is about assigning responsibility clearly. The model should reason, but it should not carry the whole system. Skills package repeatable expertise. Hooks enforce rules at the right moments. Tools handle facts and actions. RAG grounds answers when external knowledge is needed, but it should not replace databases, calculators, or clean workflow design.\nThe most reliable agentic systems are not the ones with the most freedom. They are the ones with the clearest contracts, the best evidence paths, the smallest useful skills, and the strongest feedback loops. Build the system so the agent can succeed, and design every boundary so it can fail safely.\nHopefully this will add more order in the chaos of the AI world, where if you see everything is a prompt. You can also follow my work on my github account https://github.com/ankitagrahari\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/system-design-with-agentic-ai-1/","section":"Posts","summary":"\u003cp\u003eAgentic AI fails less often because of the model and more often because the system around the model was vague. The agent was given too much freedom, too little context, weak tools, no memory strategy, and no clear way to recover when things went wrong.\u003c/p\u003e\n\u003cp\u003eA good agent is not a chatbot with extra prompts. It is a system that can plan, call tools, inspect results, revise its path, and stop at the right time. That means \u003cstrong\u003esystem design\u003c/strong\u003e matters. The planner, tools, skills, hooks, memory, retrieval, guardrails, and evaluation loop all shape whether the agent behaves like a careful assistant or a noisy automation script.\u003c/p\u003e","title":"System Design with Agentic AI - 1"},{"content":"In this blog post, we will try to send WhatsApp message in Java using Twilio account.\nPrerequisite:\nCreate a free account in Twilio. Once verified, you will have your own account SID and authorization token. Thereafter, you need to activate the Sandbox. Now add the number +14155238886 in you mobile account and send a message \u0026ldquo;join up-post\u0026rdquo; (without quotes). This will register your mobile number with Twilio sandbox and now you are eligible to send WhatsApp messages. The sandbox will look like below: Now we will write simple java code to initiate the engine and send message using program to any registered number.\nMaven Dependency #Add maven dependency for Twilio java library\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.twilio.sdk\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;twilio\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;7.51.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; After adding the maven dependency, create a new program with the below method and global variables.\nprivate String ACCOUNT_SID=\u0026#34;ABCDEFGHIJKLMNOPQRSTVWXYZ\u0026#34;; private String AUTH_TOKEN=\u0026#34;123abc456def789ghi\u0026#34;; private void sendMessageUsingTwilio(){ Twilio.init(ACCOUNT_SID, AUTH_TOKEN); Message message = Message.creator( new com.twilio.type.PhoneNumber(\u0026#34;whatsapp:+xxxxxxxxxxx\u0026#34;), new com.twilio.type.PhoneNumber(\u0026#34;whatsapp:+14155238886\u0026#34;), \u0026#34;Hello there!\u0026#34;) .create(); System.out.println(message.getSid()); } The Message.creator takes three arguments: to, from and message. You can get the account SID and Auth token from the Twilio dashboard. Custom HTTP Client #The Twilio Java helper library also provide custom ***HttpClient ***which can be created like this. Twilio used org.apache.http.client package for its custom HttpClient. Add the below lines before creating and sending the message like Message.creator() method.\nProxiedTwilioClientCreator clientCreator = new ProxiedTwilioClientCreator( ACCOUNT_SID, AUTH_TOKEN, PROXY_HOST, PROXY_PORT); TwilioRestClient twilioRestClient = clientCreator.getClient(); Twilio.setRestClient(twilioRestClient); You can find the entire code here on my Github repository.\nReferences # https://www.twilio.com/blog/what-does-twilio-do https://www.twilio.com/docs/whatsapp/quickstart/java https://www.twilio.com/docs/libraries/java https://www.twilio.com/docs/libraries/java/custom-http-clients-java NOTE: # This is a fun project but always remember not to share your SID or authorization token details. You should also verify that you don\u0026rsquo;t mistakenly shared it with the code. Twilio is not free and is having a trial period of 14 days. Hope you will enjoy as much as I enjoyed trying it out. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/","section":"Posts","summary":"\u003cp\u003eIn this blog post, we will try to send WhatsApp message in Java using \u003ca href=\"https://www.twilio.com/docs/whatsapp\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eTwilio account\u003c/em\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003ePrerequisite:\u003c/em\u003e\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eCreate a free account in Twilio.\u003c/li\u003e\n\u003cli\u003eOnce verified, you will have your own account SID and authorization token.\u003c/li\u003e\n\u003cli\u003eThereafter, you need to activate the Sandbox.\u003c/li\u003e\n\u003cli\u003eNow add the number +14155238886 in you mobile account and send a message \u0026ldquo;\u003cem\u003e\u003cstrong\u003ejoin up-post\u003c/strong\u003e\u003c/em\u003e\u0026rdquo; (without quotes).\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThis will register your mobile number with Twilio sandbox and now you are eligible to send WhatsApp messages. The sandbox will look like below:\n\n\n\n\n\n\n\u003cfigure\u003e\n    \n    \n\n\n\n\n\n\n\n\n  \n    \u003cpicture\n      class=\"mx-auto my-0 rounded-md\"\n      \n    \u003e\n      \n      \n      \n      \n        \u003csource\n          \n            srcset=\"https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_ef346730a3cee89c.webp 330w,https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_da52392d3e17a7e5.webp 660w\n            \n              \n                ,https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_e9b948193f8b67bf.webp 767w\n              \n            \n            \n              \n                ,https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_e9b948193f8b67bf.webp 767w\n              \n            \"\n          \n          sizes=\"100vw\"\n          type=\"image/webp\"\n        /\u003e\n      \n      \u003cimg\n        width=\"767\"\n        height=\"319\"\n        class=\"mx-auto my-0 rounded-md\"\n        \n        loading=\"lazy\" decoding=\"async\"\n        \n          src=\"https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_74ee35d0bbc800de.jpg\" srcset=\"https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_88800dc4e4c8b1c7.jpg 330w,https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1_hu_74ee35d0bbc800de.jpg 660w\n          \n            ,https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1.jpg 767w\n          \n          \n            ,https://dynamicallyblunttech.com/posts/using-twilio-for-sending-whatsapp-message-in-java/images/1.jpg 767w\n          \"\n          sizes=\"100vw\"\n        \n      /\u003e\n    \u003c/picture\u003e\n  \n\n\n\u003c/figure\u003e\n\nNow we will write simple java code to initiate the engine and send message using program to any registered number.\u003c/p\u003e","title":"Using Twilio for Sending WhatsApp Message in Java"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/%23746/","section":"Tags","summary":"","title":"#746"},{"content":"In this post we will look into the the leetcode #746 problem to find the minimum cost of climbing stairs. This is also a good example to start your journey to learn dynamic programming.\nProblem Statement #You are given an integer array cost where cost[i] is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps. You can either start from the step with index 0, or the step with index 1.\nReturn the minimum cost to reach the top of the floor.\nExample 1: Input: cost = [10,15,20] Output: 15 Explanation: You will start at index 1.\nPay 15 and climb two steps to reach the top.* The total cost is 15.* Example 2: Input: cost = [1,100,1,1,1,100,1,1,100,1] Output: 6 Explanation: You will start at index 0.\nPay 1 and climb two steps to reach index 2.* Pay 1 and climb two steps to reach index 4.* Pay 1 and climb two steps to reach index 6.* Pay 1 and climb one step to reach index 7.* Pay 1 and climb two steps to reach index 9.* Pay 1 and climb one step to reach the top.* The total cost is 6. * Constraints: 2 \u0026lt;= cost.length \u0026lt;= 1000\n0 \u0026lt;= cost[i] \u0026lt;= 999* Algorithm/Logic #In this DP problem we will solve the subset of the problem, store the result and then solve next subset of the problem and combine all the result to get the final output. Here we should always start from the end of the given array.\nIn order to calculate the minimum, we have to pick the minimum of the two steps at any given time and add the value at the given index. Once the minimum to get to that step is calculated, it will store it in the current step variable and then move backwards on that index.\nOnce we have iterated all the indexes, then we only have to get the minimum of the two steps variable we have used before.\nThe code will look something like this:\nint steps = cost.length; int step1 = 0, step2 = 0; for(int i=steps-1; i\u0026gt;=0; i--){ int curStep = cost[i] + Math.min(step1, step2); step1 = step2; step2 = curStep; } return Math.min(step1, step2); The above solution takes 1ms and 40mb to execute.\nYou can find the complete code here on my github repository. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/lc746-minimum-cost-of-climbing-stairs/","section":"Posts","summary":"\u003cp\u003eIn this post we will look into the the \u003ca href=\"https://leetcode.com/problems/min-cost-climbing-stairs/\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eleetcode #746\u003c/em\u003e\u003c/a\u003e problem to find the minimum cost of climbing stairs. This is also a good example to start your journey to learn dynamic programming.\u003c/p\u003e\n\u003ch3 id=\"problem-statement\" class=\"relative group\"\u003e\u003cstrong\u003eProblem Statement\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem-statement\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eYou are given an integer array cost where \u003cem\u003e\u003cstrong\u003ecost[i]\u003c/strong\u003e\u003c/em\u003e is the cost of \u003cem\u003e\u003cstrong\u003eith\u003c/strong\u003e\u003c/em\u003e step on a staircase. Once you pay the cost, you can either climb one or two steps. You can either start from the step with index 0, or the step with index 1.\u003c/p\u003e","title":"#LC746 - Minimum Cost Of Climbing Stairs"},{"content":"In Part 1 of this series, we introduced the Smart DevOps Assistant (SDA) — an AI-powered helper that reviews pull requests, generates tests, and writes scrum updates.\nNow it’s time to get our hands dirty. In this post, we’ll:\nScaffold core APIs (/generate-summary, /analyze-pr) Wire up our first prompt templates Connect Spring AI to an LLM Run our very first end-to-end test By the end, you’ll have a working backend service that can summarize PRs and hint at improvements. 🚀\nStep 1 — Project Setup #Our stack stays the same: Java 21, Spring Boot 3.x, Spring AI, ChromaDB.\nAdd the related dependencies required for Spring AI starter (check spring.io docs for the latest):\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.ai\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-ai-advisors-vector-store\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.ai\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-ai-starter-model-ollama\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; We’ll also add the Chroma dependency (not used fully yet, but needed later for context injection):\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.ai\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-ai-starter-vector-store-chroma\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Application config (application.yml):\n# Ollama - Chat spring.ai.ollama.base-url=http://localhost:11434 spring.ai.ollama.chat.options.model=llama3.2 spring.ai.ollama.chat.options.temperature=0.7 ## Ollama - Embeddings spring.ai.ollama.embedding.options.model=hf.co/mixedbread-ai/mxbai-embed-large-v1 spring.ai.ollama.embedding.base-url=http://localhost:11434 spring.ai.ollama.init.pull-model-strategy=when_missing If you are using OpenAI, then use the following\n# OpenAI spring.ai.openai.api-key=YOUR_OPENAI_API_KEY spring.ai.openai.base-url=http://localhost:12434/engines spring.ai.openai.chat.options.model=ai/gemma3 Configuration related to the chromaDB vector store\n# Chroma Vector Store connection properties spring.ai.vectorstore.chroma.client.host=http://localhost spring.ai.vectorstore.chroma.client.port=8000 # Chroma Vector Store collection properties spring.ai.vectorstore.chroma.initialize-schema=true spring.ai.vectorstore.chroma.collection-name=prCollection Step 2 — Define Core APIs #We’ll have following endpoints:\nPOST /ai/pr-summary → short overview of a PR POST /ai/pr-analyze → deeper review with suggestions POST /ai/vectorstore → store changed files in a PR to vector store. @RestController @RequestMapping(\u0026#34;/ai\u0026#34;) public class AIController { private final AIService aiService; private final SDAVectorStoreService sdaVectorStoreService; public AIController( AIService aiService, SDAVectorStoreService sdaVectorStoreService) { this.aiService = aiService; this.sdaVectorStoreService = sdaVectorStoreService; } @PostMapping(\u0026#34;/pr-summary\u0026#34;) public ResponseEntity\u0026lt;PRSummaryResponse\u0026gt; generateSummary( @RequestBody PRSummaryRequest prSummaryRequest){ return aiService.generateSummary( prSummaryRequest); } @PostMapping(\u0026#34;/pr-analyze\u0026#34;) public PRSuggestionResponse analyzePR( @RequestBody AnalyzePRRequest request){ return aiService.analyzePR( request.prDiff(), request.fileNames()); } @PostMapping(\u0026#34;/vectorstore\u0026#34;) public void storeDataToVectorStore( @RequestBody List\u0026lt;GitChangedFile\u0026gt; gitChangedFileList){ sdaVectorStoreService.populateVectorStore( gitChangedFileList); } } This keeps things API-first, and our endpoints are defined here.\nStep 3 — Prompt Templates #Spring AI’s PromptTemplate lets us define reusable, dynamic prompts.\nPromptTemplate summaryTemplate = new PromptTemplate(\u0026#34;\u0026#34;\u0026#34; You are a senior software reviewer. Summarize the following PR in 2-3 sentences. Highlight potential risks if visible. PR Title: {title} PR Description: {description} PR Diff: {diff} \u0026#34;\u0026#34;\u0026#34;); We can now inject title, description, and diff dynamically.\nFor analysis:\nPromptTemplate analyzeTemplate = new PromptTemplate(\u0026#34;\u0026#34;\u0026#34; You are a senior software engineer. Given this PR diff: {diff}, provide: 1. A short summary 2. Suggestions for improvements 3. Risks to watch for Respond in JSON: { \u0026#34;summary\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;suggestions\u0026#34;: [\u0026#34;...\u0026#34;], \u0026#34;risks\u0026#34;: [\u0026#34;...\u0026#34;] } \u0026#34;\u0026#34;\u0026#34;); Step 4 — Connect with the LLM #Spring AI makes this easy using ChatClient.\npublic PRSummaryResponse generateAISummary( PRSummaryRequest prSummaryRequest){ PromptTemplate summaryTemplate = new PromptTemplate(\u0026#34;\u0026#34;\u0026#34; You are a senior software reviewer. Summarize the following PR in 2-3 sentences. Highlight potential risks if visible. PR Title: {title} PR Description: {description} PR Diff: {diff} \u0026#34;\u0026#34;\u0026#34;) return chatClient .prompt(summaryTemplate.create(Map.of( \u0026#34;title\u0026#34;, prSummaryRequest.getTitle(), \u0026#34;description\u0026#34;, prSummaryRequest.getDescription(), \u0026#34;diff\u0026#34;, prSummaryRequest.getDiff()))) .call() .entity(PRSummaryResponse.class); } Notice how the response is mapped into a DTO (PRSummaryResponse) — no parsing headaches and dynamic substitution in the prompt.\nStep 5 — A Taste of Context Injection #public PRSuggestionResponse analyzePR(String prDiff, List\u0026lt;String\u0026gt; fileNames){ PromptTemplate analyzePRTemplate = new PromptTemplate(\u0026#34;\u0026#34;\u0026#34; You are a senior software engineer. Given this PR diff: {diff}, provide: 1. A short summary 2. Suggestions for improvements 3. Risks to watch for Respond in JSON: { \u0026#34;summary\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;suggestions\u0026#34;: [\u0026#34;...\u0026#34;], \u0026#34;risks\u0026#34;: [\u0026#34;...\u0026#34;] } \u0026#34;\u0026#34;\u0026#34;) String files = String.join(\u0026#34;\u0026#39;,\u0026#39;\u0026#34;, fileNames); //This query will list down the file content from vector db, and inject it with the prompt to provide context-aware suggestion QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor .builder(vectorStore) .searchRequest(SearchRequest.builder() .similarityThreshold(SIMILARITY_THRESHOLD) .filterExpression(\u0026#34;path in [\u0026#39;\u0026#34;+ String.join(\u0026#34;\u0026#39;,\u0026#39;\u0026#34;, fileNames) + \u0026#34;\u0026#39;]\u0026#34;) .topK(5) .build()) .build(); PRSuggestionResponse response = chatClient .prompt(analyzePRTemplate.create( Map.of(\u0026#34;diff\u0026#34;, prDiff))) .advisors(qaAdvisor) .call() .entity(PRSuggestionResponse.class); logger.debug(\u0026#34;Response:{}\u0026#34;, response); return response; } This ensures only relevant code snippets (like UserService.java) are injected into the model’s context. Flow The Spring AI framework creates an ChatClientRequest from user’s Prompt along with an empty advisor context object.\nEach advisor in the chain processes the request, potentially modifying it. Alternatively, it can choose to block the request by not making the call to invoke the next entity. In the latter case, the advisor is responsible for filling out the response.\nThe final advisor, provided by the framework, sends the request to the Chat Model.\nThe Chat Model’s response is then passed back through the advisor chain and converted into ChatClientResponse. Later includes the shared advisor context instance.\nEach advisor can process or modify the response.\nThe final ChatClientResponse is returned to the client by extracting the ChatCompletion.\n*source: https://docs.spring.io/spring-ai/reference/api/advisors.html *\nQuestionAnswerAdvisor provides capabilities to utilize a vector store to answer the queries implementing Retrieval Augment Generation (RAG) pattern.\nStep 6 — First End-to-End Run #Example request to /generate-summary:\n{ \u0026#34;title\u0026#34;: \u0026#34;Add new API generate-summary\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Adding new API to get PR summary connecting to LLM .\u0026#34;, \u0026#34;pr_Url\u0026#34;: \u0026#34;https://github.com/ankitagrahari/smart-devops-assistant/pull/3\u0026#34; } Example response:\n{ \u0026#34;summary\u0026#34;: \u0026#34;This PR (#3) adds a new API `/generate-summary` to the Smart DevOps Assistant. It introduces DTOs (PRSummaryRequest/Response), a new GitService to fetch diffs via GitHub API, and extends AIService to generate PR summaries. The controller and webhook service are updated to orchestrate diff fetching and AI calls. Documentation is updated and test utilities are added.\u0026#34;, \u0026#34;risk\u0026#34;: [ \u0026#34;GitHub PAT may expose security risks if not scoped minimally\u0026#34;, \u0026#34;Large PR diffs may exceed AI token/prompt limits, causing failures\u0026#34;, \u0026#34;Blocking synchronous RestTemplate calls may slow webhook responses\u0026#34;, \u0026#34;Error handling is inconsistent (null returns, generic 404s/500s)\u0026#34;, \u0026#34;Appending .diff to PR URLs may break for enterprise/custom GitHub setups\u0026#34;, \u0026#34;Lack of tests for the new /generate-summary endpoint and service logic\u0026#34;, \u0026#34;AI outputs may hallucinate or generate inaccurate summaries\u0026#34; ], } Boom 💥 — your assistant just summarized your PR.\nStep 7 — Gotchas # Dependency drift: Spring AI evolves quickly — check docs for version bumps. Prompt drift: Always enforce strict JSON output when expecting structured data. Response failures: Add logging to catch when the model doesn’t respect schema. Step 8 — What’s Next #Now that we have a working API + prompt flow, in Blog 3 we’ll:\nEmbed our repo into ChromaDB Chunk \u0026amp; index code/docs Run true RAG queries for context-rich reviews That’s where SDA starts feeling intelligent, not just chatty.\nDo share your thoughts about any improvements.\nGithub repo: https://github.com/ankitagrahari/smart-devops-assistant\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/building-the-foundation-apis-prompts-and-first-runs/","section":"Posts","summary":"\u003cp\u003eIn \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/meet-smart-devops-assistant-vision-stack-and-journey-so-far/\"\u003ePart 1\u003c/a\u003e of this series, we introduced the \u003cstrong\u003eSmart DevOps Assistant (SDA)\u003c/strong\u003e — an AI-powered helper that reviews pull requests, generates tests, and writes scrum updates.\u003c/p\u003e\n\u003cp\u003eNow it’s time to get our hands dirty. In this post, we’ll:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eScaffold core APIs (/generate-summary, /analyze-pr)\u003c/li\u003e\n\u003cli\u003eWire up our first \u003cstrong\u003eprompt templates\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eConnect Spring AI to an LLM\u003c/li\u003e\n\u003cli\u003eRun our very first \u003cstrong\u003eend-to-end test\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eBy the end, you’ll have a working backend service that can summarize PRs and hint at improvements. 🚀\u003c/p\u003e","title":"Building the Foundation: APIs, Prompts, and First Runs"},{"content":"As we discussed in the previous post on Domain 2: Tool Design and MCP Integration, this part of the post will cover the Domain 3 which holds 20% weightage in the exam. #If you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. #Where to Register: https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\nUnofficial practice set, written to match publicly reported blueprint topics — not sourced from the real exam bank. Use for concept drilling, not as a guarantee of exam content.\nTopics Covered #CLAUDE.md Hierarchy, Scoping \u0026amp; Modularity\nCustom Slash Commands\nClaude Skills\nPath-Specific Rules (conditional loading)\nPlan Mode vs Direct Execution\nIterative Refinement\nCI/CD Pipeline Integration (non-interactive mode)\nTeam Project Setup / Shared Configuration\nCLAUDE.md Hierarchy, Scoping \u0026amp; Modularity #CLAUDE.md is a memory file Claude Code automatically reads for persistent project context (coding standards, architecture notes, commands to run). It exists at multiple scopes:\nUser-level (~/.claude/CLAUDE.md) — personal preferences, applies across all your projects, not shared with the team Project-level (.claude/CLAUDE.md or root CLAUDE.md, checked into git) — shared with the whole team via version control Files can also be modular — split into smaller imported files instead of one giant file, so only relevant sections load. Q1. A team complains that a coding-standards rule works on the lead engineer\u0026rsquo;s machine but not for the rest of the team, even though everyone pulled the latest repo. What\u0026rsquo;s the most likely cause?\nA) Claude Code is broken\nB) The rule was placed in the lead\u0026rsquo;s user-level ~/.claude/CLAUDE.md instead of the project-level, version-controlled CLAUDE.md\nC) The team needs a different model\nD) CLAUDE.md doesn\u0026rsquo;t support rules\nAnswer: B — a very commonly tested exam pattern: user-level config isn\u0026rsquo;t shared; project-level config (in the repo) is.\nQ2. Why would you split a large CLAUDE.md into multiple smaller, imported files rather than one monolithic file?\nA) Claude Code requires it\nB) It has no practical benefit\nC) Modularity keeps unrelated context out of every session, so only relevant sections load and consume tokens\nD) Single files are always better\nAnswer: C.\nQ3. A monorepo has a root CLAUDE.md with general conventions, and a backend/CLAUDE.md with backend-specific rules. What does this demonstrate?\nA) Hierarchical scoping — nested CLAUDE.md files let context be specific to the part of the codebase being worked on\nB) An error — only one CLAUDE.md is allowed per repo\nC) User-level configuration\nD) Plan Mode\nAnswer: A.\nCustom Slash Commands #Reusable, saved prompts invoked with /command-name, stored as files (e.g., .claude/commands/review.md), so a team doesn\u0026rsquo;t retype a long instruction (\u0026ldquo;run our code review checklist\u0026rdquo;) every time. Can accept arguments.\nQ4. A team runs the exact same 6-step manual code-review prompt in every PR review session. What\u0026rsquo;s the appropriate fix?\nA) Nothing — retype it each time for accuracy\nB) Put it in Plan Mode\nC) Save it as a custom slash command (e.g., /review) so it\u0026rsquo;s invoked consistently with one command\nD) Convert it into an MCP server\nAnswer: C.\nQ5. What\u0026rsquo;s the main functional difference between a simple custom slash command and a Claude Skill?\nA) There is no difference\nB) Skills support additional capabilities — bundled supporting files, automatic invocation by Claude without the user typing the command, and more complex execution — while custom commands are simpler saved prompts\nC) Slash commands can only run once\nD) Skills cannot take arguments\nAnswer: B.\n3. Claude Skills #An evolution of custom commands — a packaged capability that can include instructions plus supporting files (scripts, templates, reference docs), and critically, can be invoked automatically by Claude when it judges the skill is relevant, not only when a user explicitly types a slash command.\nQ6. A team wants Claude to automatically apply their PDF-generation logic whenever a user asks for \u0026ldquo;a report,\u0026rdquo; without the user needing to remember a specific command. What fits best?\nA) A user-level CLAUDE.md note\nB) A Claude Skill, since it supports automatic invocation based on relevance, unlike a plain slash command that requires explicit typing\nC) Plan Mode\nD) A path-specific rule\nAnswer: B.\nQ7. True/False: Claude Skills can bundle supporting files (e.g., templates or scripts) alongside instructions, while a basic custom command is just saved prompt text.\nAnswer: True.\n4. Path-Specific Rules (Conditional Loading) #Rules or context that only load/apply when Claude is working within a specific directory/path (e.g., a rule that only applies inside frontend/), rather than being loaded globally for the whole project. Keeps irrelevant context out of sessions working elsewhere in the repo.\nQ8. A monorepo has both a Java backend and a React frontend. Frontend-specific linting conventions keep getting suggested during backend work, cluttering responses. What\u0026rsquo;s the fix?\nA) Delete the frontend conventions entirely\nB) Scope the frontend conventions as a path-specific rule tied to the frontend/ directory, so they only load when Claude is working there\nC) Merge everything into one CLAUDE.md with no structure\nD) Switch to user-level config\nAnswer: B.\nQ9. What\u0026rsquo;s the main benefit of path-specific rules over one global CLAUDE.md?\nA) They reduce cost/noise by only injecting relevant context for the area of the codebase actually being touched\nB) They replace the need for CLAUDE.md entirely\nC) They only work with MCP servers\nD) They apply globally regardless of path, ignoring the name\nAnswer: A.\n5. Plan Mode vs Direct Execution #Plan Mode restricts Claude to read-only actions (reading files, searching, analyzing) to build an implementation plan before any changes are made — no file edits, no commits, no state changes. Once approved, execution happens separately.\nDirect execution skips this — Claude reads and acts in the same flow. Plan Mode is for higher-stakes/larger changes where you want a review checkpoint; direct execution suits small, low-risk tasks.\nQ10. A developer asks Claude to refactor a critical payment module touching 15 files. What\u0026rsquo;s the recommended approach?\nA) Direct execution immediately, no review\nB) Plan Mode first — let Claude research and propose a plan, review/approve it, then execute — since this is a large, higher-risk change\nC) Skip planning since Claude Code doesn\u0026rsquo;t support it\nD) Use a slash command instead\nAnswer: B.\nQ11. While in Plan Mode, can Claude modify a file to test a hypothesis?\nA) Yes, freely\nB) No\nC) Only with sudo\nD) Only for non-code files\nAnswer: B — Plan Mode is restricted to read-only actions (reading, searching, analyzing); it cannot create, modify, or delete files, or run state-changing commands\nQ12. For a one-line typo fix in a README, is Plan Mode necessary?\nA) Yes, always required\nB) Plan Mode is required for any file edit\nC) Plan Mode and direct execution are the same thing\nD) No, direct execution is required\nAnswer: D — direct execution is appropriate for small, low-risk, unambiguous changes; Plan Mode overhead isn\u0026rsquo;t justified here\nQ13. What\u0026rsquo;s the recommended pattern for a mid-to-large feature: Plan → immediate execution, or Plan → written spec file → review → execute?\nA) Plan → immediate execution is always better\nB) Plan → spec file → review/iterate → execute\nC) Neither — always skip planning\nD) Spec files are not supported\nAnswer: B — mirrors how senior engineers work: plan and get the design reviewed before writing code. Committing the spec gives a reviewable, version-controlled artifact before code changes happen\n6. Iterative Refinement #Treating Claude Code\u0026rsquo;s output as a first draft to be reviewed and refined across multiple passes, rather than expecting a single perfect result — e.g., generate, review, give targeted feedback, regenerate, until it meets the bar.\nQ14. Claude Code generates a first implementation that mostly works but misses two edge cases. What\u0026rsquo;s the appropriate next step?\nA) Give targeted, specific feedback on the two missing edge cases and let Claude refine the existing implementation\nB) Discard everything and start over with a longer prompt\nC) Accept it as-is since first drafts are always final\nD) Switch to a different tool\nAnswer: A — iterative refinement over one-shot perfection.\nQ15. Why is iterative refinement often more reliable than trying to write one giant, perfectly detailed prompt upfront?\nA) It isn\u0026rsquo;t — one-shot prompting is always superior\nB) Claude Code doesn\u0026rsquo;t support multi-turn conversations\nC) Complex tasks often have details that only surface once you see a first attempt; refining against real output catches issues a spec alone might miss\nD) It uses fewer tokens overall in every case\nAnswer: C.\n7. CI/CD Pipeline Integration (Non-Interactive Mode) #Running Claude Code non-interactively inside automated pipelines (e.g., via the -p / print flag) — no back-and-forth chat, just a prompt in, structured result out, suitable for CI steps like automated PR review or test generation.\nQ16. A team wants Claude Code to automatically review every pull request as a CI step and post structured comments, with no human sitting at a terminal. What execution mode fits?\nA) Interactive Plan Mode session\nB) Non-interactive mode (e.g., -p flag) invoked as a pipeline step, producing structured output the pipeline can parse\nC) A custom slash command run manually\nD) User-level CLAUDE.md configuration\nAnswer: B.\nQ17. Why is structured (not free-text) output particularly important when Claude Code runs inside a CI/CD pipeline?\nA) It looks nicer\nB) Structured output is required for Plan Mode only\nC) Downstream pipeline steps need to programmatically parse the result (e.g., pass/fail, list of issues) rather than a human reading prose\nD) It has no bearing on CI/CD\nAnswer: C.\nQ18. In a CI/CD context, why is an interactive session (waiting for user approval mid-task) unsuitable?\nA) It isn\u0026rsquo;t unsuitable, it\u0026rsquo;s preferred\nB) Pipelines run unattended; a step waiting on human input would hang/block the pipeline, so non-interactive/no-prompt execution is required\nC) CI/CD doesn\u0026rsquo;t support Claude Code at all\nD) Interactive mode is faster in CI\nAnswer: B.\n8. Team Project Setup / Shared Configuration #Structuring a project so that Claude Code behaves consistently across every team member\u0026rsquo;s machine — shared, version-controlled CLAUDE.md, commands, skills, and MCP config at the project level, vs. individual/personal preferences kept at the user level.\nQ19. A new engineer joins the team and clones the repo. Without any manual setup on their\npart, they should immediately get the team\u0026rsquo;s coding standards, custom commands, and project MCP servers. What must be true of the project\u0026rsquo;s configuration?\nA) It\u0026rsquo;s all stored in each individual\u0026rsquo;s user-level config, so nothing to do\nB) Team configuration is not possible in Claude Code\nC) The relevant CLAUDE.md, .claude/commands/, skills, and .mcp.json are project-scoped and checked into the repository, so cloning the repo brings them along automatically\nD) Each engineer must manually retype all standards\nAnswer: C.\nQ20. What\u0026rsquo;s a good reason to keep some configuration (e.g., a personal Slack MCP server, individual editor preferences) at the user level instead of project level?\nA) User-level config is always required\nB) It\u0026rsquo;s specific to one person\u0026rsquo;s workflow/tools and shouldn\u0026rsquo;t be forced on the whole team via the shared repo config\nC) Project-level configuration doesn\u0026rsquo;t support personalization at all\nD) There\u0026rsquo;s no valid reason; everything should be project-level\nAnswer: B.\nRealistic Scenario-Based Question (cross-topic, exam style) #Q21. A team\u0026rsquo;s PR-review command works differently depending on which engineer runs it, despite everyone using the same slash command file from the repo. Investigation shows some engineers also have a personal ~/.claude/CLAUDE.md with conflicting review conventions. What\u0026rsquo;s happening, and what\u0026rsquo;s the fix?\nA) Slash commands are broken; nothing to do with CLAUDE.md\nB) User-level CLAUDE.md content is layering on top of project-level context, causing inconsistent behavior; align personal configs or move the conflicting rule to project level so it\u0026rsquo;s authoritative for everyone\nC) This is expected and not fixable\nD) Restart Claude Code\nAnswer: B — hierarchy/scoping conflict, a realistic diagnostic-style question.\nQ22. For a large feature spanning both backend and frontend, an architect wants:\n(1) no code changes until a plan is reviewed,\n(2) the plan captured as a reviewable artifact,\n(3) backend and frontend conventions to apply only in their respective directories during execution.\nWhich combination of Domain 3 features satisfies all three requirements?\nA) Direct execution + a single global CLAUDE.md\nB) CI/CD non-interactive mode only\nC) Claude Skills alone, with no Plan Mode\nD) Plan Mode → written spec file → review → execute, combined with path-specific rules for backend/ and frontend/\nAnswer: D.\nQ23. A CI pipeline step runs Claude Code non-interactively to auto-generate release notes from commit history, but the step keeps failing because Claude sometimes returns explanatory prose mixed with the release notes, which breaks the parser. What\u0026rsquo;s the root cause and fix?\nA) Missing structured output enforcement\nB) Non-interactive mode isn\u0026rsquo;t supported for this use case\nC) The fix is to switch to Plan Mode\nD) The fix is to make the CLAUDE.md file larger\nAnswer: B — the prompt/config should require a strict output format so the pipeline can reliably parse it, consistent with CI/CD needing structured, parseable results\nQ24. A monorepo team wants a skill that automatically kicks in whenever someone asks for \u0026ldquo;test coverage\u0026rdquo; analysis, without needing to remember a slash command name, and it needs to reference a bundled coverage-threshold config file. What Domain 3 feature is purpose-built for this, and why not a plain slash command?\nA) Plain slash command — identical capability\nB) A Claude Skill — it supports automatic invocation based on relevance and can bundle supporting files, which a basic slash command cannot do\nC) Path-specific rule — unrelated to this use case\nD) User-level CLAUDE.md\nAnswer: B.\nQUICK REFERENCE TABLE # Concept One-line definition CLAUDE.md Persistent memory file Claude Code reads automatically for project context User-level config ~/.claude/ — personal, not shared with team Project-level config .claude/ in repo — shared via version control Modular CLAUDE.md Split into smaller imported files to reduce irrelevant context Custom slash command Saved, reusable prompt invoked via /name Claude Skill Packaged capability: instructions + files + can auto-invoke Path-specific rule Context that only loads for a specific directory Plan Mode Read-only research/planning phase before any changes Direct execution Read + act in the same flow, no planning checkpoint Iterative refinement Treating output as a draft to review and improve across passes Non-interactive mode (-p) Runs Claude Code unattended for CI/CD, no chat back-and-forth Team project setup Project-scoped, version-controlled shared configuration Note: Domain 3 questions on the real exam tend to be diagnostic (\u0026ldquo;why isn\u0026rsquo;t X working across the team?\u0026rdquo;) rather than pure definition recall.\nReferring to these questions just gives you an idea how the exam looks like, and can vary. Share if you have any answers or questions not correctly placed or explained.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/cca-f-domain-3-claude-code-configuration-and-workflows-key-concepts/","section":"Posts","summary":"\u003ch3 id=\"as-we-discussed-in-the-previous-post-on-domain-2-tool-design-and-mcp-integration-this-part-of-the-post-will-cover-the-domain-3-which-holds-20-weightage-in-the-exam\" class=\"relative group\"\u003eAs we discussed in the previous post on \u003ca href=\"/post/cca-f-domain-2-tool-design-and-mcp-integration-key-concepts\"\u003eDomain \u003c/a\u003e\u003ca href=\"/post/cca-f-domain-2-tool-design-and-mcp-integration-key-concepts\"\u003e2: Tool Design and MCP Integration\u003c/a\u003e, this part of the post will cover the Domain 3 which holds 20% weightage in the exam. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#as-we-discussed-in-the-previous-post-on-domain-2-tool-design-and-mcp-integration-this-part-of-the-post-will-cover-the-domain-3-which-holds-20-weightage-in-the-exam\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003ch3 id=\"if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" class=\"relative group\"\u003eIf you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eWhere to Register: \u003ca href=\"https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\u003c/a\u003e\u003c/p\u003e","title":"CCA-F Domain 3: Claude Code Configuration and Workflows - Key Concepts"},{"content":"This post will discuss on the Fork Join framework introduced in Java 7. It utilizes the maximum available processors to compute any recursive task. So e.g. calculating the Fibonacci series or finding a number in the huge array can be done in very less time.\nHow it Works #Lets understand some key points in this framework\nFork means spawning a new task. Join means to join the pending task output. Fork/Join uses the Divide and Conquer which divides the entire dataset into small chunks for computation or applying any specific logic. Once each task is computed independently, it will merge the output of the computation to get the final result. It runs all the forked tasks in parallel using the available capacity of your machine on which it is running. To run the tasks in parallel, it uses the number of processors available to JVM which is Runtime.getRuntime().availableProcessors() number of threads. Each of these threads have double ended Queue (De-queue). In a de-queue you can add or remove an elements from the head or tail. Using de-queue, it applies the work-stealing algorithm, where if there are no tasks for a thread to execute, it will steal the tasks from other threads. This approach ensures efficient execution of a big task, utilizing the available processors of your machine.\nClasses #ForkJoinPool class is a common shared thread pool which can be accessed using commonPool() static method. This will invoke classes which extends RecursiveAction or RecursiveTask. These Recursive classes implements ForkJoinTask interface.\nWe can return a specific value (like Future in Executor framework) from class extending RecursiveTask (Callable) and class extending RecursiveAction (Runnable) will not return any values. These classes have compute() method, which holds the logic for your tasks.\nWith fork/join, there will be a way to solve any problem for certain size threshold. But if the threshold is crossed it follows this order\nCreate two tasks, Fork the first task Call compute() on the second task Call join() on the first task. This order is mandatory to follow. Here first the task is forked and we compute the task and then join it using the join() method. We will go through two examples to understand this better.\nFinding the frequency of duplicate element in an Array Finding the Fibonacci number at a given index. Problem I - Frequency of Duplicate Element in an Array #The problem statement is to find an element in an array and how many times it is duplicated.\nNow here the array size can be anything, ranging from 1 to millions.\nHere the logic will be to maintain the start and end index of the array.\nprivate static final int THRESHOLD = 47; int[] arr; int start, end, toFind; There will be one part where we put the logic to get the duplicate element frequency like this\nfor (int i = start; i \u0026lt;= end; i++) { if (toFind == arr[i]) { count++; } } System.out.println(\u0026#34;count:\u0026#34;+ count + \u0026#34; from start:\u0026#34;+ start +\u0026#34; and end:\u0026#34;+ end); return count; And other part will be when the size of the Array is crossing the Threshold limit.\nI have put 47 as threshold, because it is something used in the Sort method of Arrays class.\nCountDuplicate task1 = new CountDuplicate(arr, 0, end/2, toFind); CountDuplicate task2 = new CountDuplicate(arr, (end/2)+1, arr.length-1, toFind); task1.fork(); return task2.compute() + task1.join(); CountDuplicate is the class which computes the frequency of the duplicate elements in a specific index range of the given array.\nAnd in the main method, we will initiate the ForkJoinPool and the invoke the CountDuplicate task. The invoke method will return an Object. It can return an Integer if while specifying it is given as the RecursiveTask\u0026lt;Integer\u0026gt;.\nForkJoinPool pool = ForkJoinPool.commonPool(); int[] arr = {1, 4, 2, 1, 2, 1, 23, 1, 5, 5, 2, 4, 9, 2, 8, 3, 5, 1, 0, 6, 3, 1}; CountDuplicate task = new CountDuplicate(arr, 0, arr.length-1, 1); Integer count = pool.invoke(task); System.out.println(count); The above print method will print the count of element 1 which is repeated 6 times in the given array.\nProblem II - Fibonacci Number at an Index #Here the problem statement is to find the Fibonacci number at a given index starting from 0. The way we calculate the Fibonacci number is to add the previous two number from the list. Now every element can be computed in subtasks.\nThe first part is to calculate the Fibonacci number if the number is below the Threshold level\nInteger computeDirectly(){ int i=2; int a=0, b=1, curr; if(to\u0026lt;=1) return 1; while(i\u0026lt;=to){ curr = a + b; a = b; b = curr; i++; } return b; } Then the other part will be to get the recursive logic working if the index is more than the Threshold level.\nFibonacci task1 = new Fibonacci(to-1); Fibonacci task2 = new Fibonacci(to-2); task1.fork(); return task2.compute() + task1.join(); Here task1 calculates the Fibonacci at n-1 index and task2 will calculate the fibonacci at n-2 index. The final result will be the summation of both the values.\nNOTE: You can notice in both the example above that the fork() is called on the first task to create the subtask, then the compute() is called on the second subtask to recursively process it and then the join() on the first task again. The join() method should be called at last because it will block the next task from being processed until the result is returned.\nThe Arrays.parallelSort() method also using this Fork/Join framework to sort the arrays. You can find the parallel sort example here.\nYou can also find the entire code base in my github repository. You can refer to the solution of problem1 here and for problem 2 here.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/fork-join-framework-in-java/","section":"Posts","summary":"\u003cp\u003eThis post will discuss on the Fork Join framework introduced in Java 7. It utilizes the maximum available processors to compute any recursive task. So e.g. calculating the Fibonacci series or finding a number in the huge array can be done in very less time.\u003c/p\u003e\n\u003ch2 id=\"how-it-works\" class=\"relative group\"\u003eHow it Works \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#how-it-works\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eLets understand some key points in this framework\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eFork means spawning a new task.\u003c/li\u003e\n\u003cli\u003eJoin means to join the pending task output.\u003c/li\u003e\n\u003cli\u003eFork/Join uses the \u003cem\u003e\u003cstrong\u003eDivide and Conquer\u003c/strong\u003e\u003c/em\u003e which divides the entire dataset into small chunks for computation or applying any specific logic. Once each task is computed independently, it will merge the output of the computation to get the final result.\u003c/li\u003e\n\u003cli\u003eIt runs all the forked tasks in parallel using the available capacity of your machine on which it is running.\u003c/li\u003e\n\u003cli\u003eTo run the tasks in parallel, it uses the number of processors available to JVM which is \u003cem\u003eRuntime.getRuntime().availableProcessors()\u003c/em\u003e number of threads.\u003c/li\u003e\n\u003cli\u003eEach of these threads have \u003cem\u003e\u003cstrong\u003edouble ended Queue (De-queue)\u003c/strong\u003e\u003c/em\u003e. In a de-queue you can add or remove an elements from the head or tail.\u003c/li\u003e\n\u003cli\u003eUsing de-queue, it applies the \u003cem\u003e\u003cstrong\u003ework-stealing algorithm\u003c/strong\u003e\u003c/em\u003e, where if there are no tasks for  a thread to execute, it will steal the tasks from other threads.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThis approach ensures efficient execution of a big task, utilizing the available processors of your machine.\u003c/p\u003e","title":"Fork-Join Framework in Java"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/genai/","section":"Categories","summary":"","title":"GenAI"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/rag/","section":"Categories","summary":"","title":"RAG"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/rag/","section":"Tags","summary":"","title":"RAG"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/sda/","section":"Categories","summary":"","title":"SDA"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/sda/","section":"Tags","summary":"","title":"SDA"},{"content":"Spring boot application provides a quick and elegant way to create REST API\u0026rsquo;s with minimal boiler plate code and resolves any dependency that is present in the classpath.\nIn this plethora of new technologies era, it is evident that your spring boot application can have multiple configuration files which is configured, to be used by different parts of your code.\nFor example the database specific configuration can go in a different location which more secure, while an environment specific configuration is kept in a different configuration file. Its more on how the design and implementation goes.\nIn this blog post, we will focus on different ways to read configuration files in a spring boot application. Lets start by creating a sample spring boot application using Spring initializer with Spring Web and Lombok as dependencies. We will create a sample Rest controller which will read some configurable properties from the properties file.\nDefault Configuration #Whenever we create a spring boot application, under the /src/main/resources directory, you can find an application.properties file, which is the default configuration file for your spring boot application. You would specify any number of parameters as key value pairs, which can be read by your code as per necessary.\nThis is the ***default ***configuration file which spring boot automatically can finds it in the\n*classpath root *or under classpath config package. It can also look\nunder current directory or child of current directory or child of child of current directory to find files of application.properties or the application.yaml type.\nWe can guide spring boot to look for the default configuration file in some other directory as well using @PropertySource annotation, which we will look in some time.\nThere are different ways to pass the configurable values to your project other than the default configuration file.\n@Value annotation #How To #This is more real world scenarios to extract key value pairs from the configuration file. It can be used with the parameters we want in the configuration file like this\n@Value(\u0026#34;${app.config.name}\u0026#34;) private String appConfigName; Here the app.config.name parameter is injected in the *appConfigName *String variable.\nAlso, this brings to one important thing is @Value annotation accepts only String value which further can be casted to other datatype by Spring.\n@Value annotation can get environment variables or system variables or configurable variables defined in some configuration (**.properties or *.yaml or yml) files.\nDefault Value #We can give a default value with @Value annotation like this:\n@Value(\u0026#34;${app.config.name:DBTConfigReader}\u0026#34;) private String appConfigName; This is to handle scenarios where the app.config.name is not found in the configuration files, then it will default it to \u0026ldquo;DBTConfigReader\u0026rdquo;.\nSpEL #We can use spring expression language to refer to a list of values or map and do some manipulations on it.\nGet the entire properties file using:\n@Value(\u0026#34;#{systemProperties}\u0026#34;) private Map\u0026lt;String, String\u0026gt; properties; or can get any specific parameter:\nin case of Map, it is like doing a get on the map\u0026rsquo;s key:\n@Value(\u0026#34;#{systemProperties[\u0026#39;app.config.group.map\u0026#39;]}\u0026#34;) private Map\u0026lt;String, String\u0026gt; appConfigGroupMap; in case of List, we could use something like this:\n@Value(\u0026#34;#{\u0026#39;${app.config}\u0026#39;.split(\u0026#39;,\u0026#39;)}\u0026#34;) private List\u0026lt;String\u0026gt; appConfigs; even with the case where we have to get Map, for entries where parameter has values separated by delimiter like comma, we can use following:\n@Value(\u0026#34;#{${app.config.entries}}\u0026#34;) private Map\u0026lt;String, List\u0026lt;String\u0026gt;\u0026gt; entries; From Environment #Spring core framework provides another way to read configuration values using Environment interface based on the profile which is selected.\nHere we are going to use the @PropertySource to explicitly tell Spring that when this configuration is loaded, the configuration parameters needs to be accessed from the given configuration files present in the classpath. We can autowire the Environment interface which will injected with the object during the runtime by Spring.\nimport com.dbt.appconfreader.entity.AppConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; @Configuration @PropertySource(\u0026#34;classpath:env.properties\u0026#34;) public class EnvConfigReader { @Autowired private Environment env; public AppConfig appConfig(){ AppConfig obj = new AppConfig(); obj.setKey(\u0026#34;system.name\u0026#34;); obj.setValue(env.getProperty(\u0026#34;system.name\u0026#34;)); obj.setMessage(\u0026#34;JavaHome:\u0026#34;+ env.getProperty(\u0026#34;java.home\u0026#34;)); return obj; } } You will notice if you run this class, that the value for *JavaHome *will be from the environment variable set and not from the configuration files.\nNOTE*: Make sure that the property key is not duplicated from the default configuration properties file, else the default will take the precedence. *\nFrom Beans # Spring Boot provides an alternative method of working with properties that allows strongly typed beans to govern and validate the configuration of your application.\nFor the specific properties file like\nserver.port=8798 app.config.profile=DEV app.config.name=dev-DynamicallyBluntTech app.config.type=dev-TechBlog app.config.entry=DEV Application Configuration Reader create a bean out of it, which can be injected when necessary.\nimport lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Getter @Setter @Component @ConfigurationProperties(prefix = \u0026#34;app.config\u0026#34;) @PropertySource(\u0026#34;classpath:dev.properties\u0026#34;) public class DevProperties { private String name; private String profile; private String type; private String entry; } For nested level values, we can create public static class that can be populated with the nested level of property name. For the following sample properties file:\nserver.port=8798 app.config.profile=DEV app.config.name=dev-DynamicallyBluntTech app.config.type=dev-TechBlog app.config.entry=DEV Application Configuration Reader app.config.classA.name=A-DBT_Nested app.config.classA.type=A-nested app.config.classA.value=A-IN-USE app.config.classB.name=B-DBT_Nested app.config.classB.type=B-nested app.config.classB.value=B-IN-USE We can observe that the nested level is there on the app.config.classA and app.config.classB\nfor which we can create the Bean as this:\n@Getter @Setter @Component @ConfigurationProperties(prefix = \u0026#34;app.config\u0026#34;) @PropertySource(\u0026#34;classpath:dev.properties\u0026#34;) public class DevProperties { private String name; private String profile; private String type; private String entry; private ClassA classA; private ClassB classB; @Getter @Setter public static class ClassA{ private String name; private String type; private String value; } @Getter @Setter public static class ClassB{ private String name; private String type; private String value; } } This will get the values injected under each nested level properly.\nWe were mostly talking about reading property files till now, but lets see reading the *yaml *files instead if properties file.\nFrom YAML configuration #When using the @Value annotation, we don\u0026rsquo;t have to change anything here and can get the values present as part of the configuration files. Same is the case with *Environment *interface.\nThe problem comes with @PropertySource, which cannot read *YAML *configuration files. So, in order to find something to make it work with, we can utilize the Spring *YamlPropertySourceLoader *which load the .yml or .yaml files into the *PropertySource *abstract class.\n@Bean public static YamlPropertySourceLoader propertyYamlConfigInDev() { return new YamlPropertySourceLoader(); } We also have to define the *YAML *factory which will take the *.yml or *.yaml files and convert to the property files to read the configuration parameters.\nSo, now we have the factory class, which takes the *yaml *and convert to *PropertySource *object like this\npublic class YamlSourceFactory implements PropertySourceFactory { @Override public PropertySource\u0026lt;?\u0026gt; createPropertySource( String name, EncodedResource resource) { YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean(); factoryBean.setResources(resource.getResource()); Properties properties = factoryBean.getObject(); return new PropertiesPropertySource( resource.getResource().getFilename(), properties); } } And the configuration class will look like this:\n@Getter @Configuration @PropertySource(value = \u0026#34;classpath:diff.yaml\u0026#34;, factory = YamlSourceFactory.class) public class YamlConfigReader { @Value(\u0026#34;${app.config.name:DBTConfigReader}\u0026#34;) private String appConfigName; @Value(\u0026#34;${app.config.type}\u0026#34;) private String appConfigType; @Bean public static YamlPropertySourceLoader propertyYamlConfigInDev() { return new YamlPropertySourceLoader(); } } And this concludes all the ways to read the configuration files in Spring boot application. You can find all the code base on my *Github *link.\nReferences: # https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config Hope this will help you in your learning process. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/spring-boot-application-external-configurations/","section":"Posts","summary":"\u003cp\u003eSpring boot application provides a quick and elegant way to create REST API\u0026rsquo;s with minimal boiler plate code and resolves any dependency that is present in the \u003cem\u003eclasspath\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eIn this plethora of new technologies era, it is evident that your spring boot application can have multiple configuration files which is configured, to be used by different parts of your code.\u003c/p\u003e\n\u003cp\u003eFor example the database specific configuration can go in a different location which more secure, while an environment specific configuration is kept in a different configuration file. Its more on how the design and implementation goes.\u003c/p\u003e","title":"Spring Boot Application External Configurations"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/crud/","section":"Categories","summary":"","title":"CRUD"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/crud/","section":"Tags","summary":"","title":"CRUD"},{"content":"In this post we will create a Java application which will interact with MongoDB to perform all the CRUD operations.\nWhat is NoSQL database. #To start with, these database are non-relational database, but that doesn\u0026rsquo;t mean that the NoSQL database cannot maintain relationship. These database are different from relational database in the way the data is stored. NoSQL data model keeps the related data nested to single data structure.\nIt can be called as \u0026ldquo;non SQL\u0026rdquo; or \u0026ldquo;not only SQL\u0026rdquo; database.\nWhat is MongoDB ? #As stated on MongoDB official website:\nMongoDB is a document database with the scalability and flexibility that you want with the querying and indexing that you need\nMongoDB is a NoSQL database which stores data in ***JSON ***like documents, means that the number/type of fields can vary from document to document. It is a distributed database at its core, so high availability, horizontal scaling and easy to use.\nAnd on top of it, its free to use. So lets use it to create, update, retrieve and delete (CRUD) data from MongoDB using Java.\nBasics #Database #In MongoDB, database is collection of documents. A single MongoDB server can have multiple databases.\nYou can create database using below command.\n\u0026gt; use mydb switched to db mydb \u0026gt; dbs mydb Collections It will access the database if it exists, else it will create the database when you store data to it. The command \u0026ldquo;dbs\u0026rdquo; will let you know which database you are accessing.\nCollections #In MongoDB, Collections can be compared to tables in relational database. Only change here is that these are non tabular. The below command can be used to create collection.\ndb.createCollection(\u0026lt;collectionName\u0026gt;) To list down all the collections in the database, use below command\ndb.getCollectionNames() Documents #In MongoDB, Documents can be compared to rows in relational database. Only change here is that these documents are JSON like records.\n{ \u0026#34;_id\u0026#34; : { \u0026#34;$oid\u0026#34; : \u0026#34;5ede054470d5755464d4f37a\u0026#34; }, \u0026#34;name\u0026#34; : \u0026#34;NewDoc\u0026#34;, \u0026#34;type\u0026#34; : \u0026#34;database\u0026#34;, \u0026#34;count\u0026#34; : 5, \u0026#34;versions\u0026#34; : [\u0026#34;v3.2\u0026#34;, \u0026#34;v3.0\u0026#34;, \u0026#34;v2.6\u0026#34;], \u0026#34;info\u0026#34; : { \u0026#34;x\u0026#34; : 203, \u0026#34;y\u0026#34; : 102 } } Perform CURD operation on MongoDB #We will create a Java project which will\nAdd the mongoDB dependency, \u0026amp;\nConnect to the database \u0026amp;\nAccess the specific databases, \u0026amp;\nList all the databases, \u0026amp;\nCreate and Access Collections, \u0026amp;\nCreate Documents, \u0026amp;\nList all documents, \u0026amp;\nRetrieve specific Docuements, \u0026amp;\nUpdate specific Document, \u0026amp;\nDelete specific Documents\nDependencies #To include monogdb in your project you can use the following maven dependency in your pom.xml file.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mongodb\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mongo-java-driver\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.4.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Create MongoClient Object #Like with any other database we will create a MongoClient object to connect to the mongoDB database. We can use MongoClient or MongoClientURI to create the client object.\nCreate client for default host localhost and default port 27017: MongoClient client = new MongoClient(); Create client for given host and default port 27017: MongoClient client = new MongoClient(\u0026#34;localhost\u0026#34;); Create client for given host and port: MongoClient client = new MongoClient(\u0026#34;localhost\u0026#34;, 27017); Create client using the connection string: MongoClientURI connectionString = new MongoClientURI( \u0026#34;mongodb://localhost:27017\u0026#34;); MongoClient mongoClient = new MongoClient(connectionString); Accessing given Database #Once we have created the MongoClient object, we can get specific database using getDatabase() method.\n//Access given database MongoDatabase db = client.getDatabase(\u0026#34;mydb\u0026#34;); System.out.println(db.getName()); List all Database names #Using the MongoClient object created above, we can list down all the databases created using ***listDatabaseNames() ***method.\n//Listing all the database MongoIterable\u0026lt;String\u0026gt; itr = client.listDatabaseNames(); for (MongoCursor\u0026lt;String\u0026gt; it = itr.iterator(); it.hasNext(); ) { System.out.println(it.next()); } Create and Access Collection #Using the MongoClient object, we can create a new collection and access the new collection using getCollection()** **method.\n//Create a collection under mydb db.createCollection(\u0026#34;mycol\u0026#34;); //Access given (mycol) collection MongoCollection\u0026lt;Document\u0026gt; collection = db.getCollection(\u0026#34;mycol\u0026#34;); Create Documents #We have to create Document object to store each record and will use the collection created above to insert these documents to specific collection.\nDocument document = new Document(\u0026#34;name\u0026#34;, \u0026#34;NewDoc\u0026#34;) .append(\u0026#34;type\u0026#34;, \u0026#34;database\u0026#34;) .append(\u0026#34;count\u0026#34;, 1) .append(\u0026#34;versions\u0026#34;, Arrays.asList(\u0026#34;v3.2\u0026#34;, \u0026#34;v3.0\u0026#34;, \u0026#34;v2.6\u0026#34;)) .append(\u0026#34;info\u0026#34;, new Document(\u0026#34;x\u0026#34;, 203).append(\u0026#34;y\u0026#34;,102)); collection.insertOne(document); System.out.println(\u0026#34;After adding one document to the collection, total count of documents is \u0026#34;+collection.count()); After adding the document to the collection, we can get the count of documents present in the collections using collections.count() method.\nWe can insert either one document or list of documents to a collection.\nMongoCollection\u0026lt;Document\u0026gt; collection = db.getCollection(\u0026#34;mycol\u0026#34;); List\u0026lt;Document\u0026gt; documents = new ArrayList\u0026lt;\u0026gt;(); Document document1 = new Document(\u0026#34;name\u0026#34;, \u0026#34;SecDoc\u0026#34;) .append(\u0026#34;type\u0026#34;, \u0026#34;middleware\u0026#34;) .append(\u0026#34;count\u0026#34;, 2) .append(\u0026#34;versions\u0026#34;, Arrays.asList(\u0026#34;v1.2\u0026#34;, \u0026#34;v1.0\u0026#34;)); Document document2 = new Document(\u0026#34;name\u0026#34;, \u0026#34;ThirdDoc\u0026#34;) .append(\u0026#34;type\u0026#34;, \u0026#34;database\u0026#34;) .append(\u0026#34;count\u0026#34;, 3) .append(\u0026#34;versions\u0026#34;, Arrays.asList(\u0026#34;11g\u0026#34;, \u0026#34;12c\u0026#34;, \u0026#34;18c\u0026#34;, \u0026#34;19c\u0026#34;)); documents.add(document1); documents.add(document2); collection.insertMany(documents); Now if you execute the collections.count() method, you will get count as 2.\nList all Documents #Like listing all the databases, you can list down all the documents using the find() method which provides the iterator on the MongoCursor and even print it in *JSON *format using toJSON() method.\npublic static void listAllDocuments(MongoCollection\u0026lt;Document\u0026gt; collection){ System.out.println(\u0026#34;Listing all documents in the collection in JSON format\u0026#34;); MongoCursor\u0026lt;Document\u0026gt; cursor = collection.find().iterator(); while(cursor.hasNext()){ System.out.println(cursor.next().toJson()); } } We can also use the collection.find().first() method to get the first document/record from the collection.\nRetrieving Documents #In order to fetch some documents on desired constraints, we can use the find() method with the *Filters *class which contains all the utilities that can be used in the finding the documents. This is analogous to the where clause in relational database.\nWe have to statically import *Filters *class\nimport static com.mongodb.client.model.Filters.and; import static com.mongodb.client.model.Filters.eq; And the same can be used in the find() method to apply the where clause.\nMongoDatabase db = client.getDatabase(\u0026#34;mydb\u0026#34;); MongoCollection\u0026lt;Document\u0026gt; collection = db.getCollection(\u0026#34;mycol\u0026#34;); Document docTypeMiddleware = collection.find( eq(\u0026#34;type\u0026#34;,\u0026#34;middleware\u0026#34;) ).first(); System.out.println(\u0026#34;Document where type=middleware: \u0026#34;); System.out.println(docTypeMiddleware.toJson()); Document docTypeDBCount1 = collection.find( and(eq(\u0026#34;type\u0026#34;, \u0026#34;database\u0026#34;), eq(\u0026#34;count\u0026#34;, 1) ) ).first(); System.out.println(\u0026#34;Document where type=database and count=1\u0026#34;); System.out.println(docTypeDBCount1.toJson()); Update Documents #We can update the documents created using updateMany() or updateOne() methods. It returns *UpdateResult *class with which we can get the count of records updated.\nMongoDatabase db = client.getDatabase(\u0026#34;mydb\u0026#34;); MongoCollection\u0026lt;Document\u0026gt; collection = db.getCollection(\u0026#34;mycol\u0026#34;); UpdateResult updateResult = collection.updateMany( eq(\u0026#34;type\u0026#34;,\u0026#34;database\u0026#34;), new Document(\u0026#34;$set\u0026#34;, new Document(\u0026#34;count\u0026#34;, 5)) ); System.out.println(updateResult.getModifiedCount()); this.listAllDocuments(collection); The last line will call the above method which will list all the documents in that collection.\nDelete Documents #We can also delete documents which is comparable to deleting rows from tables in relational database. It return DeleteResult*** ***class, using which we can get the number of records deleted.\nMongoDatabase db = client.getDatabase(\u0026#34;mydb\u0026#34;); MongoCollection\u0026lt;Document\u0026gt; collection = db.getCollection(\u0026#34;mycol\u0026#34;); DeleteResult deleteResult = collection.deleteOne( eq(\u0026#34;type\u0026#34;, \u0026#34;middleware\u0026#34;) ); System.out.println(deleteResult.getDeletedCount()); This concludes the creating, retrieving, updating and deleting a document (all CRUD operation) in MongoDB. These are some basic operation to start using a NoSQL database. It has a lot of other thing, but this should be a good starting point to use it your projects for basic operations.\nYou can find the entire code base in this *Github *repository.\nReference # https://docs.mongodb.com/manual/introduction/ https://mongodb.github.io/mongo-java-driver/3.4/driver/getting-started/quick-start/ Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/crud-operation-in-mongodb/","section":"Posts","summary":"\u003cp\u003eIn this post we will create a Java application which will interact with MongoDB to perform all the CRUD operations.\u003c/p\u003e\n\u003ch2 id=\"what-is-nosql-database\" class=\"relative group\"\u003e\u003cstrong\u003eWhat is NoSQL database.\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#what-is-nosql-database\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eTo start with, these database are non-relational database, but that doesn\u0026rsquo;t mean that the NoSQL database cannot maintain relationship. These database are different from relational database in the way the data is stored. NoSQL data model keeps the related data nested to single data structure.\u003c/p\u003e","title":"CRUD operation in MongoDB"},{"content":"Part 3 of the StreamMetrics Series\n*Previous parts: Part 1: Kafka Producer | Part 2: Consumer + DLQ | * Part 4: From Localhost to Kubernetes\nBuilding production-grade streaming analytics: windows, state stores, and performance validation\nIn Parts 1 and 2, we built a Kafka producer and consumer that process individual events reliably. But processing 10,000 raw events per second creates a new problem:\nHow do you extract insights from that fire hose of data?\nEnter Kafka Streams - a library that lets you build real-time aggregations, transformations, and joins directly on top of Kafka. No external databases, no batch jobs, no delays. Events arrive, get aggregated in-memory, and results publish to output topics - all in milliseconds.\nThis post covers the StreamMetrics aggregation layer:\nhow we compute rolling averages, min/max values, and event counts every minute, store results in Redis for instant queries, and validate the entire pipeline handles 10K events/second with zero message loss.Overview of this blog post Performance validated: 600,000 events processed in 60 seconds = 9,999 events/sec with zero failures.\nArchitecture: From Raw Events to Aggregates # Architecture Data reduction: 600,000 raw events per minute compressed to ~60 aggregates per minute = 10,000x reduction\n1. Kafka Streams: The 30-Second Explanation #Kafka Streams is a Java library that treats Kafka topics as infinite streams you can transform. Unlike traditional consumers that just read-process-ack, Kafka Streams lets you:\nFilter and map (stateless) Aggregate and join (stateful - backed by RocksDB) Window data by time Maintain fault-tolerant state (backed up to Kafka changelog topics) 1.1 Why not just use a consumer + database?\nYou could. But then you need to:\nQuery 600K events every minute from your database Calculate averages in application code Handle database failures, connection pools, indexes Scale your database to handle 10K writes/sec Kafka Streams does all of this for you, in-memory, with state backed up to Kafka. If your Streams app crashes, it restarts and rebuilds state from the changelog topic. No external database needed for the aggregation logic.\n2. Building the Aggregation Topology #Step 1: Read from Input Topic #KStream\u0026lt;String, MetricEvent\u0026gt; inputStream = builder .stream(inputTopic, Consumed.with(Serdes.String(), metricEventSerde)); This creates a stream that continuously reads from metrics-events topic. Every new event that arrives becomes an item in the stream.\nStep 2: Group by Service + Metric #KGroupedStream\u0026lt;String, MetricEvent\u0026gt; grouped = inputStream .groupBy( (key, value) -\u0026gt; value.getServiceId() + \u0026#34;:\u0026#34; + value.getMetricName(), Grouped.with(Serdes.String(), metricEventSerde) ); We repartition data so all events for the same service+metric go to the same partition. This is critical for correct aggregation. The new key looks like: payment-service:api.request.duration\nWarning: groupBy() triggers a repartition - data is shuffled across partitions. This is expensive but necessary.\nStep 3: Define Time Windows #TimeWindows window = TimeWindows .ofSizeWithNoGrace(Duration.ofMinutes(1)); Tumbling windows: non-overlapping, fixed-size time buckets.\nEvents with timestamps 10:00:00-10:00:59 go in window 1, events 10:01:00-10:01:59 go in window 2, etc.\nNo grace period: Late events (arriving after the window closes) are dropped. In production, you\u0026rsquo;d add a grace period: ofSizeAndGrace(Duration.ofMinutes(1), Duration.ofMinutes(5))\nStep 4: Aggregate #This is where statistics are computed in real-time:\nKTable\u0026lt;Windowed\u0026lt;String\u0026gt;, AggregatedMetric\u0026gt; aggregated = grouped .windowedBy(window) .aggregate( // Initializer: create empty aggregate for each new window () -\u0026gt; AggregatedMetric.builder() .count(0L) .sum(0.0) .min(Double.MAX_VALUE) .max(Double.MIN_VALUE) .build(), // Aggregator: called for EVERY event in the window (key, value, aggregate) -\u0026gt; { aggregate.setCount(aggregate.getCount() + 1); aggregate.setSum( aggregate.getSum() + value.getValue()); aggregate.setMin( Math.min(aggregate.getMin(), value.getValue())); aggregate.setMax( Math.max(aggregate.getMax(), value.getValue())); aggregate.setAvg( aggregate.getSum() / aggregate.getCount()); return aggregate; }, // State store: persisted to RocksDB + Kafka changelog Materialized.with(Serdes.String(), aggregatedMetricSerde) ); 2.1 How the aggregator works - example execution: #Window: 10:00:00 - 10:00:59\nKey: payment-service:api.request.duration\nEvent 1 arrives (value=50):\ncount: 0 + 1 = 1\nsum: 0 + 50 = 50\nmin: min(MAX, 50) = 50\nmax: max(MIN, 50) = 50\navg: 50/1 = 50.0\nEvent 2 arrives (value=60):\ncount: 1 + 1 = 2\nsum: 50 + 60 = 110\nmin: min(50, 60) = 50\nmax: max(50, 60) = 60\navg: 110/2 = 55.0\nEvent 3 arrives (value=45):\ncount: 2 + 1 = 3\nsum: 110 + 45 = 155\nmin: min(50, 45) = 45\nmax: max(60, 45) = 60\navg: 155/3 = 51.67\nAt 10:01:00 → Window closes → Aggregate sent to output topic\nStep 5: Publish to Output Topic #outputStream.to(outputTopic, Produced.with(Serdes.String(), aggregatedMetricSerde)); When each window closes (every 1 minute), the final aggregate is published to metrics-aggregated-1m topic. A separate consumer picks it up and stores it in Redis.\n3. State Stores: How Kafka Streams Remembers #Aggregations need memory. For each service+metric+window combination, Kafka Streams maintains running totals (count, sum, min, max). This state lives in two places:\nLocal State: RocksDB\nEach Streams instance has a local embedded RocksDB database (default location: /tmp/kafka-streams). This is fast - microsecond lookups - because it\u0026rsquo;s on the same machine.\n3.1 Remote State: Kafka Changelog Topics #Every state change is also written to a Kafka changelog topic (auto-created, named like applicationId-KSTREAM-AGGREGATE-STATE-STORE-0000000001-changelog). This is your backup.\nIf your Streams app crashes, it can rebuild its entire local state by replaying the changelog topic. This makes Kafka Streams fault-tolerant without needing an external database.\nTrade-off: Writes are slower (every state change writes to Kafka), but you get fault tolerance for free. In production, this is almost always worth it.\n4. Performance Testing: Hitting 10K Events/Sec #The original producer REST endpoint (POST /produce?events=100) was single-threaded and slow. To validate real throughput, we built a dedicated load test application.\n4.1 Load Test Design\nTarget: 10,000 events/second Duration: 60 seconds (600,000 total events) Threads: 20 producer threads Rate limiting: Precise nanosecond timing to hit exact throughput ExecutorService executor = Executors.newFixedThreadPool(20); // Each thread sends (targetRate / numThreads) events per second int eventsPerThreadPerSecond = 10000 / 20; // 500 events/sec/thread // Precise rate limiting with nanosecond sleep long delayNanos = TimeUnit.SECONDS.toNanos(1) / eventsPerSecond; long nextSendTime = System.nanoTime(); while (running) { kafkaTemplate.send(topic, event).whenComplete((result, ex) -\u0026gt; { if (ex == null) successCount.incrementAndGet(); else failureCount.incrementAndGet(); }); nextSendTime += delayNanos; long sleepNanos = nextSendTime - System.nanoTime(); if (sleepNanos \u0026gt; 0) TimeUnit.NANOSECONDS.sleep(sleepNanos); } 4.2 Results # Total sent Success Failures Duration Actual throughput 600,000 600,000 0 60,004ms 9,999 events/sec 4.3 Key optimizations for high throughput: # acks=1 instead of acks=all (less safe, much faster) Larger batch size: 65KB instead of 32KB More buffer memory: 128MB instead of 64MB Async sends with CompletableFuture (fire-and-forget) 20 concurrent producer threads 5. Querying Aggregated Data #Aggregates live in Redis with 24-hour TTL. We built a REST API to query them efficiently.\nExample 1: Get Latest Aggregate\nGET /api/metrics/latest?service=payment-service\u0026amp;metric=api.request.duration { \u0026#34;service\u0026#34;: \u0026#34;payment-service\u0026#34;, \u0026#34;metric\u0026#34;: \u0026#34;api.request.duration\u0026#34;, \u0026#34;data\u0026#34;: { \u0026#34;count\u0026#34;: 100, \u0026#34;avg\u0026#34;: 52.3, \u0026#34;min\u0026#34;: 45.2, \u0026#34;max\u0026#34;: 60.8, \u0026#34;windowStart\u0026#34;: \u0026#34;2024-12-21T10:00:00Z\u0026#34; } } Example 2: Time Series (Last 10 Minutes)\nGET /api/metrics/timeseries?service=payment-service\u0026amp;metric=api.request.duration\u0026amp;minutes=10 { \u0026#34;windows\u0026#34;: 10, \u0026#34;data\u0026#34;: [ {\u0026#34;timestamp\u0026#34;: 1771483200000, \u0026#34;avg\u0026#34;: 52.3, \u0026#34;count\u0026#34;: 100}, {\u0026#34;timestamp\u0026#34;: 1771483140000, \u0026#34;avg\u0026#34;: 51.8, \u0026#34;count\u0026#34;: 98}, {\u0026#34;timestamp\u0026#34;: 1771483080000, \u0026#34;avg\u0026#34;: 53.1, \u0026#34;count\u0026#34;: 102} ] } Example 3: Compare All Services\nGET /api/metrics/compare?metric=api.request.duration { \u0026#34;services\u0026#34;: { \u0026#34;payment-service\u0026#34;: {\u0026#34;avg\u0026#34;: 52.3}, \u0026#34;user-service\u0026#34;: {\u0026#34;avg\u0026#34;: 48.2}, \u0026#34;order-service\u0026#34;: {\u0026#34;avg\u0026#34;: 55.7} } } Data reduction benefit: Instead of querying 600K raw events for 10 minutes of data, we query 10 pre-computed aggregates. 60,000x faster\n6. Production Lessons Learned #Lesson 1: Window Timing is Event-Time, Not Wall-Clock Time\nWindows close based on the timestamp field in your events, not the current system time. If your events have timestamps from 2 hours ago, those windows close immediately. If events have future timestamps, windows stay open longer than expected.\nAlways use Instant.now() for real-time metrics. For testing, you can inject old timestamps to force windows to close immediately.\nLesson 2: State Directory Conflicts\nKafka Streams stores local state in /tmp/kafka-streams by default. If you restart your app and get \u0026lsquo;State store already exists\u0026rsquo; errors, delete this directory:\nrm -rf /tmp/kafka-streams\nLesson 3: Rebalancing is Expensive\nWhen a Streams instance starts or stops, Kafka rebalances partitions across all running instances. During rebalancing, no processing happens. In production, use static membership group.instance.id to avoid rebalances on restarts.\nLesson 4: Monitoring is Critical\nKafka Streams exposes important metrics via JMX:\nrecords-lag-max - how far behind is your consumer? commit-latency-avg - how long does state commit take? process-rate - events processed per second Export these to Prometheus and alert if lag grows or process rate drops.\n7. Final Architecture and Results #7.1 What we built:\nProducer: REST API that can sustain 10K events/sec Kafka Streams: Real-time aggregation every 1 minute Redis: Pre-computed aggregates with 24-hour TTL Query API: Instant lookups (no scanning millions of raw events) Kafka UI 7.2 Performance validated:\n600,000 events processed in 60 seconds Zero message loss Zero failures Actual throughput: 9,999 events/sec Data reduction: 10,000x (600K raw events → 60 aggregates/min) 9999 Events Processed 7.3 Use cases enabled:\nReal-time dashboards showing current avg latency per service Alerting on anomalies (avg latency \u0026gt; threshold) Capacity planning (identify peak load times) SLA monitoring (track p99 latency trends) The full code is available at: http://github.com/ankitagrahari/StreamAnalytics\nThis is the part of series where production grade application is the final result. We will add the integration test to gain more confidence in the code. And then will go with deploying the application using Kubernetes and docker. Much to come, lots to learn.\nDo provide your feedback, help me if the direction I am taking is relevant any more or not. Feel the motivation even though Claude is giving it :)\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/real-time-aggregations-with-kafka-streams-at-10k-events-sec/","section":"Posts","summary":"\u003cp\u003e\u003cstrong\u003ePart 3 of the StreamMetrics Series\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e*Previous parts: \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-a-production-kafka-producer/\"\u003e\u003cem\u003ePart 1: Kafka Producer\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-production-grade-kafka-consumer-patterns/\"\u003e\u003cem\u003ePart 2: Consumer + DLQ\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | *\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/from-localhost-to-kubernetes-deploying-streammetrics-at-scale/\"\u003e\u003cem\u003ePart 4: From Localhost to Kubernetes\u003c/em\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBuilding production-grade streaming analytics: windows, state stores, and performance validation\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn Parts 1 and 2, we built a Kafka producer and consumer that process individual events reliably. But processing 10,000 raw events per second creates a new problem:\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003eHow do you extract insights from that fire hose of data?\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e","title":"Real-Time Aggregations with Kafka Streams at 10K Events/Sec"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/streammetrics/","section":"Categories","summary":"","title":"StreamMetrics"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/streammetrics/","section":"Tags","summary":"","title":"StreamMetrics"},{"content":"In this post we will go through the integration testing of the controller of any Spring Boot project. We will be using an existing Spring boot project which was created for the previous post. First lets go through the types of testing that can be done in Microservices environment.\nTypes of Testing in Microservices #In Microservices architecture, where different servcies interact with each other it becomes a bit complex to test all the services and keeping the automated test cases will help in recognizing any shared services and the impact of change on one another services.\nThese following are different types of testing which can be done in any Microservices architecture:\nUnit Testing Component Testing Integration Testing End-to-End Testing **End-to-End/System testing ** #It covers the entire flow to test, e.g. in Amazon e-commerce website, from selecting an item to adding to the cart, and going through the payment gateway and placing an order with the order details shared with customer is all one flow, where a blockage at any endpoint will result in failure of the entire flow and will have restart again. So, lets say if some developer has changed something in the Add to cart functionality and now users are only able to add items of the same category. These kind of scenarios can be caught early in development phase using the end-to-end testing.\nIntegration Testing #During an integration testing, all the services are tested which are interacting with it. Considering the number of modules which are referenced, it can become very complex also. It test that my service is working when added to the entire Microservices present.\nComponent Testing #In component testing, Microservices is executed as black box unknown to outer world, testing the interface behavior. It tests that every single Microservice is working as per the requirements.\nUnit Testing #It includes testing of all the utilities, services or any other helping classes, which are used by the Service or Controller classes. It make the code more agile and improves the code quality. In IntelliJ or any other IDE, you can generate the Test Case for the entire class or for some specific methods.\nWriting Test for Spring Boot Project # When we create our project initially from Spring Initializr, we can see there are spring-boot-starter and spring-boot-starter-test dependency already added to the pom file. The microservices architecture will look something like this. Here endpoints will be from different services performing different task.\nDependency #We will be using the our own Junit and not the one clubbed together with the Spring starter\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-test\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;junit\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;junit\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.junit.jupiter\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;junit-jupiter-engine\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; Check for the src/test package, which will have a Test class created by Spring already as part of the initial project.\nWriting First Test #The Test class present in the *src/test *package will have @SpringBootTest annotation which tell to start the Spring application with the class having @SpringBootApplication and also states that this is the Test class for out Spring Boot application.\nWe will enable the auto configure of *MockMVC *by using the @EnableAutoConfigureMockMvc annotation. So my final class will look like this\n@AutoConfigureMockMvc @SpringBootTest class ApachepoiApplicationTests { @Autowired MockMvc mockMvc; @Test void contextLoads() {} We have auotwired the *MockMvc *class to perform any of the Rest call to different endpoints.\nYou can also notice the empty contextLoads() method which ensures that whenever you start the test run, it verifies that the Test has loaded the Spring Context properly or not.\nGet Request #Lets create a test method using *MockMvc *class which will call a specific Get Endpoint and verify the status is 200 OK and return the *MvcResult *instance. We can extract String response from *MvcResult *instance.\n@Test void readDummyExcelTest() throws Exception { MvcResult result = mockMvc.perform( MockMvcRequestBuilders .get(\u0026#34;/excel/readDummy\u0026#34;) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); System.out.println(result.getResponse().getContentAsString()); Assertions.assertNotNull(result.getResponse().getContentAsString()); Assertions.assertEquals(\u0026#34;Success\u0026#34;, result.getResponse().getContentAsString()); } Here the perform method of *MockMvc *class takes RequestBuilder which is used to construct an Get Request. In the request construct, we can set the contentType, what type to accept as response and verify if the status of the request is 200 OK. At last return a *MvcResult *object.\nAnd we are using Junit Assertions methods like assertNotNull(), assertEquals() etc. to assert the output with the expected response.\nPost Request #As the above get request test method, we will create another test for Post request, where we can set the content as String only.\n@Test void integrationPostTest() throws Exception { ExcelRowEle obj = new ExcelRowEle(); obj.setOpen(500); obj.setHigh(700); obj.setClose(678); obj.setLow(477); obj.setVolume(5623498); obj.setTypicalPrice((obj.getHigh()+obj.getLow()+obj.getClose())/3); obj.setVp(obj.getVolume()*obj.getTypicalPrice()); obj.setTotalV(obj.getVolume()); obj.setTotalVP(obj.getVp()); obj.setVwap(obj.getTotalV()/obj.getTotalVP()); MvcResult result = mockMvc.perform(MockMvcRequestBuilders .post(\u0026#34;/excel/test\u0026#34;) .content(objectMapper.writeValueAsString(obj)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String response = result.getResponse().getContentAsString(); Assertions.assertEquals(response, obj.toString()); } In the above method, we are creating a Model and setting the content with our Model(ExcelRowEle) in the JSON format.\nNOTE*: With RequestBuilder, you can only set the content or get the response as String. Then we can use ObjectMapper class to convert in JSON format.*\nYou can refer to my github repository for the full codebase. The test class is present here.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\nReferences # https://spring.io/guides/gs/testing-web/ ","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/testing-in-microservices/","section":"Posts","summary":"\u003cp\u003eIn this post we will go through the integration testing of the controller of any Spring Boot project. We will be using an existing Spring boot project which was created for the \u003ca href=\"https://www.dynamicallyblunttech.com/post/read-write-excel-using-apache-poi-java\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003eprevious post\u003c/em\u003e\u003c/a\u003e. First lets go through the types of testing that can be done in Microservices environment.\u003c/p\u003e\n\u003ch2 id=\"types-of-testing-in-microservices\" class=\"relative group\"\u003e\u003cstrong\u003eTypes of Testing in Microservices\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#types-of-testing-in-microservices\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eIn Microservices architecture, where different servcies interact with each other it becomes a bit complex to test all the services and keeping the automated test cases will help in recognizing any shared services and the impact of change on one another services.\u003c/p\u003e","title":"Testing in Microservices"},{"content":"Part 4 of the StreamMetrics Series\n*Previous parts: Part 1: Kafka Producer | Part 2: Consumer + DLQ | * Part 3: Real-Time AggregationsStreammetric Bottleneck Explainer by NotebookLM\nLove how after building something locally, you think \u0026ldquo;okay, it works on my machine!\u0026rdquo; and then reality hits when you try to deploy it. Docker says \u0026ldquo;works on my machine\u0026rdquo; is not an excuse anymore, and Kubernetes says \u0026ldquo;hold my beer, let\u0026rsquo;s make it production-ready.\u0026rdquo;\nThis is the tale of taking StreamMetrics from docker-compose on localhost to a full Kubernetes deployment with KRaft Kafka, discovering why Redis refuses to connect, why Alpine needs libstdc++, and why environment variables have a mind of their own.\nObjective for this post: Dockerize all StreamMetrics microservices, Deploy them to Kubernetes (minikube), Set up a 3-node KRaft Kafka cluster using Strimzi, and Validate the entire pipeline works end-to-end at scale. The Journey: Docker First, Kubernetes Later #Phase 1: Dockerizing Everything #We started with three Spring Boot applications that worked beautifully in development:\nProducer (port 8085): REST API for sending metrics Consumer (port 8081): Processes events with DLQ pattern Streams (port 8082): Real-time aggregations with Kafka Streams The naive approach would be a multi-stage Docker build that compiles everything inside the container. But Maven + multi-module projects + missing modules = pain. We learned this the hard way.\nThe pragmatic approach: Build locally, then Dockerize the JAR.\nProducer Dockerfile #FROM eclipse-temurin:21-jre-alpine WORKDIR /app COPY streammetrics-producer/target/streammetrics-producer-0.0.1-SNAPSHOT.jar app.jar RUN addgroup -S spring \u0026amp;\u0026amp; adduser -S spring -G spring USER spring:spring EXPOSE 8085 HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \\ CMD wget --quiet --tries=1 --spider http://localhost:8085/actuator/health || exit 1 ENTRYPOINT [\u0026#34;java\u0026#34;, \u0026#34;-jar\u0026#34;, \u0026#34;app.jar\u0026#34;] Key decisions:\nJRE-only base image: We don\u0026rsquo;t need JDK in production (250MB vs 400MB) Non-root user: Security best practice Health check: Kubernetes needs this for liveness/readiness probes Alpine: Smallest possible image\u0026hellip; or so we thought (spoiler: RocksDB had other plans) Build process: ## Step 1: Build JARs locally mvn clean package -DskipTests # Step 2: Build Docker images docker build -f streammetrics-producer/Dockerfile -t streammetrics-producer:latest . docker build -f streammetrics-consumer/Dockerfile -t streammetrics-consumer:latest . docker build -f streammetrics-streams/Dockerfile -t streammetrics-streams:latest . # Step 3: Verify docker images | grep streammetrics Testing with Docker Compose #Before Kubernetes, we validated everything with docker-compose:\nversion: \u0026#39;3.8\u0026#39; services: producer: image: streammetrics-producer:latest ports: - \u0026#34;8085:8085\u0026#34; environment: SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka1:19092,kafka2:19092,kafka3:19092 SPRING_REDIS_HOST: redis SPRING_REDIS_PORT: 6379 SPRING_DATA_REDIS_CLIENT_TYPE: lettuce MANAGEMENT_HEALTH_REDIS_ENABLED: false depends_on: - kafka1 - kafka2 - kafka3 - redis networks: - streammetrics-network Lesson learned: The MANAGEMENT_HEALTH_REDIS_ENABLED: false saved us hours. Spring Boot 4.x uses reactive Redis health checks by default, which failed spectacularly in our setup. Disabling it made everything work. Phase 2: Kubernetes Deployment #Setting Up Minikube ## Install minikube brew install minikube # Start with plenty of resources (Kafka is hungry) minikube start --cpus=4 --memory=8192 --disk-size=20g # Point Docker CLI to minikube\u0026#39;s Docker daemon eval $(minikube docker-env) # Rebuild images INSIDE minikube docker build -f streammetrics-producer/Dockerfile -t streammetrics-producer:latest . docker build -f streammetrics-consumer/Dockerfile -t streammetrics-consumer:latest . docker build -f streammetrics-streams/Dockerfile -t streammetrics-streams:latest . ```K8s Setup \u0026gt; **Critical insight:** eval $(minikube docker-env) is magic. It makes your local Docker CLI talk to minikube\u0026#39;s Docker daemon. Without this, Kubernetes can\u0026#39;t find your images because they\u0026#39;re on your host machine, not in minikube. #### **Namespace Setup** ```java apiVersion: v1 kind: Namespace metadata: name: streammetrics Create namespace\nkubectl apply -f k8s/namespace.yaml kubectl config set-context --current --namespace=streammetrics Setting the default namespace saves you from typing -n streammetrics 500 times.\nPhase 3: KRaft Kafka Cluster with Strimzi #This is where it gets interesting. We wanted KRaft mode (no Zookeeper) because:\nSimpler architecture\nFaster metadata operations\nIndustry direction (Zookeeper is deprecated)\nInstalling Strimzi Operator ## Install Strimzi operator kubectl create -f \u0026#39;https://strimzi.io/install/latest?namespace=streammetrics\u0026#39; -n streammetrics # Wait for operator kubectl wait deployment/strimzi-cluster-operator \\ --for=condition=Available --timeout=300s -n streammetrics Strimzi is Kubernetes-native Kafka. It uses Custom Resource Definitions (CRDs) to manage Kafka clusters declaratively.\nKRaft Kafka Cluster with KafkaNodePools #Modern Strimzi requires KafkaNodePools. This was a surprise.\nKafkaNodePool manifest:\napiVersion: kafka.strimzi.io/v1beta2 kind: KafkaNodePool metadata: name: broker namespace: streammetrics labels: strimzi.io/cluster: streammetrics-kafka spec: replicas: 3 roles: - broker - controller # KRaft mode: brokers ARE controllers storage: type: ephemeral resources: requests: memory: 1Gi cpu: 500m limits: memory: 2Gi cpu: 1000m Kafka cluster manifest:\napiVersion: kafka.strimzi.io/v1beta2 kind: Kafka metadata: name: streammetrics-kafka namespace: streammetrics annotations: strimzi.io/kraft: \u0026#34;enabled\u0026#34; strimzi.io/node-pools: \u0026#34;enabled\u0026#34; spec: kafka: version: 4.0.1 listeners: - name: plain port: 9092 type: internal tls: false config: offsets.topic.replication.factor: 3 transaction.state.log.replication.factor: 3 transaction.state.log.min.isr: 2 default.replication.factor: 3 min.insync.replicas: 2 entityOperator: topicOperator: {} userOperator: {} Key points:\nstrimzi.io/kraft: \u0026ldquo;enabled\u0026rdquo; - No Zookeeper strimzi.io/node-pools: \u0026ldquo;enabled\u0026rdquo; - Use new architecture No replicas in Kafka spec - moved to KafkaNodePool No storage in Kafka spec - moved to KafkaNodePool Deploy order matters:\n# 1. NodePool first kubectl apply -f k8s/kafka-nodepool.yaml # 2. Then Kafka cluster kubectl apply -f k8s/kafka-cluster.yaml # 3. Wait (3-5 minutes) kubectl wait kafka/streammetrics-kafka --for=condition=Ready --timeout=600s Creating Topics #apiVersion: kafka.strimzi.io/v1beta2 kind: KafkaTopic metadata: name: metrics-events namespace: streammetrics labels: strimzi.io/cluster: streammetrics-kafka spec: partitions: 6 replicas: 3 config: retention.ms: 86400000 # 24 hours kubectl apply -f k8s/kafka-topics.yaml kubectl get kafkatopics Phase 4: Deploying Applications #The Redis Connection Saga #This consumed hours. The error:\nUnable to connect to localhost/\u0026lt;unresolved\u0026gt;:6379 Despite setting:\nenv: - name: SPRING_REDIS_HOST value: \u0026#34;redis\u0026#34; - name: SPRING_REDIS_PORT value: \u0026#34;6379\u0026#34; The problem: Our RedisConfig class hardcoded localhost:\n@Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(\u0026#34;localhost\u0026#34;, 6379); // HARDCODED! } The solution: Inject properties:\n@Configuration public class RedisConfig { @Value(\u0026#34;${spring.redis.host:localhost}\u0026#34;) private String redisHost; @Value(\u0026#34;${spring.redis.port:6379}\u0026#34;) private int redisPort; @Bean public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); config.setHostName(redisHost); // Use injected value config.setPort(redisPort); return new LettuceConnectionFactory(config); } } Update application.yml:\nspring: redis: host: ${SPRING_REDIS_HOST:localhost} port: ${SPRING_REDIS_PORT:6379} Rebuild, redeploy (believe me, we executed this a lot):\nmvn clean package -DskipTests eval $(minikube docker-env) docker build -f streammetrics-consumer/Dockerfile -t streammetrics-consumer:latest . kubectl delete pod -l app=consumer **Finally worked! **🥳\nProducer Deployment #apiVersion: apps/v1 kind: Deployment metadata: name: producer namespace: streammetrics spec: replicas: 1 selector: matchLabels: app: producer template: metadata: labels: app: producer spec: containers: - name: producer image: streammetrics-producer:latest imagePullPolicy: Never # Use local image ports: - containerPort: 8085 env: - name: SPRING_KAFKA_BOOTSTRAP_SERVERS value: \u0026#34;streammetrics-kafka-kafka-bootstrap:9092\u0026#34; - name: SPRING_REDIS_HOST value: \u0026#34;redis\u0026#34; - name: SPRING_REDIS_PORT value: \u0026#34;6379\u0026#34; resources: requests: memory: \u0026#34;512Mi\u0026#34; cpu: \u0026#34;250m\u0026#34; limits: memory: \u0026#34;1Gi\u0026#34; cpu: \u0026#34;500m\u0026#34; --- apiVersion: v1 kind: Service metadata: name: producer namespace: streammetrics spec: type: NodePort ports: - port: 8085 targetPort: 8085 nodePort: 30085 selector: app: producer Note: imagePullPolicy: Never tells Kubernetes to use the local image we built inside minikube.\nThe RocksDB Alpine Issue #When deploying streams, we hit:\njava.lang.UnsatisfiedLinkError: /tmp/librocksdbjni.so: Error loading shared library libstdc++.so.6: No such file or directory Kafka Streams uses RocksDB for state storage.\nRocksDB is a native C++ library.\nAlpine doesn\u0026rsquo;t include libstdc++.\nFix: Update Streams Dockerfile:\nFROM eclipse-temurin:21-jre-alpine WORKDIR /app # Install required native libraries for RocksDB RUN apk add --no-cache libstdc++ COPY streammetrics-streams/target/streammetrics-streams-0.0.1-SNAPSHOT.jar app.jar # ... rest of Dockerfile Rebuild and redeploy. It worked!\nEnd-to-End Validation ## Port-forward producer kubectl port-forward svc/producer 8085:8085 \u0026amp; # Send 100 events curl \u0026#34;http://localhost:8085/produce?events=100\u0026#34; # Check consumer processed them kubectl logs deployment/consumer --tail=50 | grep \u0026#34;Successfully processed\u0026#34; # Check streams aggregated kubectl logs deployment/streams --tail=50 | grep \u0026#34;aggregate\u0026#34; # Verify Redis has data kubectl exec deployment/redis -- redis-cli KEYS \u0026#34;metrics:*\u0026#34; kubectl exec deployment/redis -- redis-cli KEYS \u0026#34;agg:1m:*\u0026#34; # Check Kafka topics kubectl exec -it streammetrics-kafka-broker-0 -- \\ bin/kafka-console-consumer.sh \\ --bootstrap-server localhost:9092 \\ --topic metrics-events \\ --from-beginning \\ --max-messages 10 Architecture: Before vs After #Before (docker-compose): #Host Machine └─ Docker ├─ Kafka (3 brokers) ├─ Redis ├─ Producer ├─ Consumer └─ Streams After (Kubernetes): #Minikube Cluster └─ Namespace: streammetrics ├─ StatefulSet: Kafka (3 pods) ├─ Deployment: Redis (1 pod) ├─ Deployment: Producer (1 pod) ├─ Deployment: Consumer (2 pods) ├─ Deployment: Streams (1 pod) ├─ Service: Kafka (ClusterIP) ├─ Service: Redis (ClusterIP) └─ Service: Producer (NodePort) Production Lessons Learned #1. eval $(minikube docker-env) is Essential #Without this, your images are on your host machine. Kubernetes in minikube can\u0026rsquo;t see them. You\u0026rsquo;ll get ImagePullBackOff errors.\n# Always run this before building images eval $(minikube docker-env) # To undo (switch back to host Docker) eval $(minikube docker-env -u) 2. imagePullPolicy Matters #For local development:\nimagePullPolicy: Never # Use local image only For production (with registry):\nimagePullPolicy: Always # Always pull from registry 3. ConfigMaps for Configuration #Instead of hardcoding config in JARs, use ConfigMaps:\napiVersion: v1 kind: ConfigMap metadata: name: consumer-config data: application.yml: | spring: kafka: bootstrap-servers: streammetrics-kafka-kafka-bootstrap:9092 redis: host: redis Mount in deployment:\nvolumeMounts: - name: config mountPath: /config volumes: - name: config configMap: name: consumer-config 4. Resource Limits Prevent Noisy Neighbors #resources: requests: # Guaranteed resources memory: \u0026#34;512Mi\u0026#34; cpu: \u0026#34;250m\u0026#34; limits: # Maximum allowed memory: \u0026#34;1Gi\u0026#34; cpu: \u0026#34;500m\u0026#34; 5. Health Checks are Critical #livenessProbe: # Restart if unhealthy httpGet: path: /actuator/health port: 8081 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: # Remove from service if not ready httpGet: path: /actuator/health port: 8081 initialDelaySeconds: 30 periodSeconds: 5 Performance Comparison # Metric docker-compose Kubernetes Startup time 30 seconds 2-3 minutes Kafka broker startup 15 seconds 45 seconds (with health checks) Throughput 9999 events/sec 9999 events/sec Scalability Manual kubectl scale deployment/consumer \u0026ndash;replicas=5 Self-healing None Automatic pod restarts Resource isolation Shared Per-pod limits What\u0026rsquo;s Next? #We now have:\n✅ Dockerized microservices\n✅ Kubernetes deployment on minikube\n✅ 3-broker KRaft Kafka cluster\n✅ Full pipeline working (producer → Kafka → streams → consumer → Redis)\n✅ Validated 10K events/sec throughput\nStill missing for production:\n⚠️ Persistent storage (PersistentVolumes) ⚠️ Monitoring (Prometheus + Grafana) ⚠️ Horizontal Pod Autoscaling ⚠️ Helm chart for easier deployment ⚠️ TLS/SSL for Kafka ⚠️ Secrets management ⚠️ CI/CD pipeline Next post: We\u0026rsquo;ll add Prometheus and Grafana for monitoring, set up Horizontal Pod Autoscaler to scale based on Kafka lag, and create a Helm chart to deploy the entire stack with one command.\nUseful Kubernetes Commands ## Check everything kubectl get all # Watch pods kubectl get pods -w # Logs kubectl logs deployment/consumer -f kubectl logs -l app=consumer --tail=100 # Exec into pod kubectl exec -it deployment/consumer -- sh # Port forwarding kubectl port-forward svc/producer 8085:8085 # Scale deployment kubectl scale deployment/consumer --replicas=3 # Restart deployment kubectl rollout restart deployment/consumer # Check resource usage kubectl top nodes kubectl top pods # Describe for debugging kubectl describe pod \u0026lt;pod-name\u0026gt; kubectl describe deployment consumer Challenges We Hit (and Fixed) #Issue 1: \u0026ldquo;Unable to connect to Redis\u0026rdquo; #Root cause: Hardcoded localhost in RedisConfigFix: Use @Value to inject from properties\nIssue 2: \u0026ldquo;UnsatisfiedLinkError: libstdc++.so.6\u0026rdquo; #Root cause: Alpine missing C++ runtime for RocksDBFix: Add RUN apk add \u0026ndash;no-cache libstdc++\nIssue 3: \u0026ldquo;No KafkaNodePools found\u0026rdquo; #Root cause: Strimzi requires NodePools for KRaftFix: Create KafkaNodePool with broker+controller roles\nIssue 4: \u0026ldquo;ImagePullBackOff\u0026rdquo; #Root cause: Image built on host, not in minikubeFix: eval $(minikube docker-env) before building\nIssue 5: Environment variables ignored #Root cause: Spring Boot config precedence issuesFix: Use ${VAR:default} syntax in application.yml\nKey Takeaways #Minikube is amazing for learning - Full Kubernetes locally\nStrimzi simplifies Kafka on K8s - Declarative, Kubernetes-native\nKRaft is production-ready - Simpler than Zookeeper\nConfiguration is hard - ConfigMaps + env vars + @Value = tricky\nNative dependencies matter - Alpine != everything just works\nKubernetes debugging is different - Learn kubectl, logs, describe\nResource limits prevent surprises - Always set requests/limits\nTry It Yourself #Full code available on GitHub: https://github.com/ankitagrahari/StreamAnalytics\n# Clone the repo git clone https://github.com/ankitagrahari/StreamAnalytics cd StreamAnalytics # Build JARs mvn clean package -DskipTests # Start minikube minikube start --cpus=4 --memory=8192 # Point Docker to minikube eval $(minikube docker-env) # Build images docker build -f streammetrics-producer/Dockerfile -t streammetrics-producer:latest . docker build -f streammetrics-consumer/Dockerfile -t streammetrics-consumer:latest . docker build -f streammetrics-streams/Dockerfile -t streammetrics-streams:latest . # Deploy everything kubectl apply -f k8s/ # Test kubectl port-forward svc/producer 8085:8085 curl \u0026#34;http://localhost:8085/produce?events=100\u0026#34; Share your thoughts whether you liked or disliked it. Do let me know if you have any queries or suggestions.\nNever forget, Learning is the primary goal.\nTags: #Kubernetes #Docker #Kafka #SpringBoot #KRaft #Strimzi #Microservices #DevOps\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/from-localhost-to-kubernetes-deploying-streammetrics-at-scale/","section":"Posts","summary":"\u003cp\u003e\u003cstrong\u003ePart 4 of the StreamMetrics Series\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e*Previous parts: \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-a-production-kafka-producer/\"\u003e\u003cem\u003ePart 1: Kafka Producer\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-production-grade-kafka-consumer-patterns/\"\u003e\u003cem\u003ePart 2: Consumer + DLQ\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | *\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/real-time-aggregations-with-kafka-streams-at-10k-events-sec/\"\u003e\u003cem\u003ePart 3: Real-Time Aggregations\u003c/em\u003e\u003c/a\u003eStreammetric Bottleneck Explainer by NotebookLM\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n\t\t\t\u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube.com/embed/2Bs8K6folk0?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n\t\t\u003c/div\u003e\n\n\u003cp\u003eLove how after building something locally, you think \u0026ldquo;\u003cem\u003eokay, it works on my machine!\u003c/em\u003e\u0026rdquo; and then reality hits when you try to deploy it. Docker says \u0026ldquo;\u003cem\u003eworks on my machine\u003c/em\u003e\u0026rdquo; is not an excuse anymore, and Kubernetes says \u0026ldquo;\u003cem\u003ehold my beer, let\u0026rsquo;s make it production-ready.\u003c/em\u003e\u0026rdquo;\u003c/p\u003e","title":"From Localhost to Kubernetes: Deploying StreamMetrics at Scale"},{"content":"Previous parts: Part 1: Kafka Producer | Part 2: Consumer + DLQ | Part 3: Real-Time Aggregations | Part 4: Docker + Kubernetes\nYou know that feeling when your app is running in production and someone asks \u0026ldquo;Is everything okay?\u0026rdquo; and you respond with \u0026ldquo;\u0026hellip;I think so?\u0026rdquo; Yeah, that\u0026rsquo;s not good enough.\nAfter deploying StreamMetrics to Kubernetes with a 3-node KRaft Kafka cluster, dockerized microservices, and validated 10K events/sec throughput, I realized I was flying blind. Sure, the pods were running. The logs looked fine. But I had no idea:\nHow much memory each service was using Whether consumer lag was building up If HTTP request latencies were spiking When Redis was about to run out of memory This is the story of adding production-grade monitoring to StreamMetrics using Prometheus and Grafana, discovering why Spring Boot metrics need special dependencies, why imported Grafana dashboards break mysteriously, and why ${DS_PROMETHEUS} is the most frustrating variable in observability.\nObjective: Set up Prometheus for metrics collection, Grafana for visualization, create custom dashboards for JVM/Kafka/Redis metrics, and configure alerts for consumer lag and pod failures. Why Monitoring Matters (Beyond \u0026ldquo;It\u0026rsquo;s Running\u0026rdquo;) #The Production Incident That Never Happened #Imagine this scenario:\nConsumer starts falling behind (lag builds to 10K messages) JVM heap slowly fills up (90% usage) GC pauses increase (300ms → 2 seconds) HTTP requests start timing out Pod gets OOMKilled Kubernetes restarts it Repeat Without monitoring: You find out when users complain. With monitoring: Alert fires at step 1, you scale before step 4.\nThat\u0026rsquo;s the difference.\nThe Monitoring Stack: Prometheus + Grafana #Why Prometheus? #Prometheus is the de-facto standard for Kubernetes monitoring because:\nPull-based: Scrapes metrics from /metrics endpoints Time-series database: Stores metrics with timestamps PromQL: Powerful query language for aggregations Service discovery: Auto-discovers pods via Kubernetes API Alert manager: Built-in alerting Why Grafana? #Grafana turns raw metrics into visual insights:\nBeautiful dashboards Supports multiple data sources (Prometheus, Loki, etc.) Templating and variables Alerting (can also use Prometheus AlertManager) JSON-based dashboard sharing The Architecture # Kubernetes Cluster Architecture Step 1: Installing Prometheus Stack with Helm #Instead of deploying Prometheus and Grafana manually (hundreds of YAML lines), we use kube-prometheus-stack - a batteries-included Helm chart.\nInstall Helm ## macOS brew install helm # Verify helm version Install Prometheus Stack ## Add Helm repo helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update # Create monitoring namespace kubectl create namespace monitoring # Install the stack helm install prometheus prometheus-community/kube-prometheus-stack \\ --namespace monitoring \\ --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \\ --set grafana.adminPassword=admin123 # Wait for pods (2-3 minutes) kubectl get pods -n monitoring -w What this installs:\nPrometheus server (metrics storage) Grafana (visualization) AlertManager (alerting) Node exporter (host-level metrics) Kube-state-metrics (Kubernetes object metrics) Prometheus operator (manages Prometheus via CRDs) The magic flag: serviceMonitorSelectorNilUsesHelmValues=false tells Prometheus to scrape ALL ServiceMonitors, not just ones with specific labels. Critical for discovering our apps.\nAccess Grafana ## Port-forward Grafana kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80 # Open browser open http://localhost:3000 # Login: admin / admin123 You should see Grafana\u0026rsquo;s home page with default Kubernetes dashboards already configured! Graphana Dashboard Step 2: Exposing Metrics from Spring Boot #Spring Boot apps don\u0026rsquo;t expose Prometheus metrics by default. We need Micrometer.\nAdd Dependencies #Update all three modules (producer, consumer, streams) pom.xml:\nxml\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.micrometer\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;micrometer-registry-prometheus\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Configure Metrics Endpoint #Update application.yml in all modules:\nmanagement: endpoints: web: exposure: include: health,info,metrics,prometheus metrics: export: prometheus: enabled: true tags: application: ${spring.application.name} environment: kubernetes What this does:\nExposes /actuator/prometheus endpoint Formats metrics in Prometheus format Adds tags to every metric (helps filter in queries) Rebuild and Redeploy #mvn clean package -DskipTests eval $(minikube docker-env) docker build -f streammetrics-producer/Dockerfile -t streammetrics-producer:latest . docker build -f streammetrics-consumer/Dockerfile -t streammetrics-consumer:latest . docker build -f streammetrics-streams/Dockerfile -t streammetrics-streams:latest . # Restart deployments kubectl rollout restart deployment/producer -n streammetrics kubectl rollout restart deployment/consumer -n streammetrics kubectl rollout restart deployment/streams -n streammetrics Verify Metrics Endpoint #kubectl port-forward -n streammetrics deployment/producer 8085:8085 \u0026amp; curl http://localhost:8085/actuator/prometheus | head -20 # Should show: # # HELP jvm_memory_used_bytes The amount of used memory # # TYPE jvm_memory_used_bytes gauge # jvm_memory_used_bytes{area=\u0026#34;heap\u0026#34;,id=\u0026#34;G1 Eden Space\u0026#34;} 1.2345678E7 # ... If you see Prometheus-formatted metrics, you\u0026rsquo;re golden! Curl Response Step 3: ServiceMonitors - Auto-Discovery Magic #Prometheus Operator uses ServiceMonitors (a Custom Resource Definition) to discover what to scrape.\nThe Pattern #For each app, we need:\nA Service that exposes the metrics port A ServiceMonitor that tells Prometheus to scrape it Producer ServiceMonitor #Create k8s/monitoring/servicemonitors/producer-servicemonitor.yaml:\napiVersion: v1 kind: Service metadata: name: producer-metrics namespace: streammetrics labels: app: producer spec: ports: - name: metrics port: 8085 targetPort: 8085 selector: app: producer --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: producer-monitor namespace: streammetrics labels: release: prometheus # Important: matches Helm release spec: selector: matchLabels: app: producer endpoints: - port: metrics path: /actuator/prometheus interval: 30s Key points:\nlabels.release: prometheus - Must match your Helm release name interval: 30s - Scrape every 30 seconds path: /actuator/prometheus - Where Spring Boot exposes metrics Create for Consumer and Streams #Repeat the same pattern for consumer (port 8081) and streams (port 8082).\nApply all ServiceMonitors:\nkubectl apply -f k8s/monitoring/servicemonitors/ # Check they exist kubectl get servicemonitor -n streammetrics # Should show: # NAME AGE # producer-monitor 10s # consumer-monitor 10s # streams-monitor 10s Verify Prometheus is Scraping ## Port-forward Prometheus kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090 \u0026amp; # Open Prometheus UI open http://localhost:9090 Go to Status → Targets Look for: streammetrics/producer-monitor, consumer-monitor, streams-monitor Status should be: UP (green) Prometheus Service Monitors If targets are UP, Prometheus is successfully scraping!\nIf targets are DOWN, check:\nServiceMonitor labels match Helm release Service selector matches pod labels Pod is actually running and healthy Step 4: Creating Grafana Dashboards #The JVM Dashboard Saga #Attempt 1: Import community dashboard ID 4701 (JVM Micrometer)\nResult: Error - \u0026ldquo;Data source ${DS_PROMETHEUS} was not found\u0026rdquo;\nWhy it fails: Imported dashboards use variables that don\u0026rsquo;t match your datasource name.\nThe fix:\nDashboard Settings → JSON Model Find all \u0026ldquo;${DS_PROMETHEUS}\u0026rdquo; Replace with \u0026ldquo;Prometheus\u0026rdquo; (your actual datasource name) Save OR - Create the variable manually:\nDashboard Settings → Variables → Add variable Name: DS_PROMETHEUS Type: Data source Query: Prometheus Save Building a Custom Dashboard (Recommended) #Instead of fighting with imported dashboards, let\u0026rsquo;s build one from scratch.\nCreate New Dashboard → Add Panels: #Panel 1: JVM Heap Memory Usage #jvm_memory_used_bytes{namespace=\u0026#34;streammetrics\u0026#34;, area=\u0026#34;heap\u0026#34;} Panel 2: CPU Usage %\nrate(process_cpu_seconds_total{namespace=\u0026#34;streammetrics\u0026#34;}[1m]) * 100 Panel 3: Kafka Messages Sent/sec #Spring Boot exposes Kafka template metrics:\nrate(spring_kafka_template_seconds_count{namespace=\u0026#34;streammetrics\u0026#34;}[1m]) Note: We discovered that Kafka Streams doesn\u0026rsquo;t expose kafka_streams_* metrics by default. Instead, we use Spring Boot\u0026rsquo;s spring_kafka_template_* metrics.\nPanel 4: Pod Status #up{namespace=\u0026#34;streammetrics\u0026#34;} Panel 5: HTTP Requests per Second #rate(http_server_requests_seconds_count{namespace=\u0026#34;streammetrics\u0026#34;}[1m]) Save dashboard as: \u0026ldquo;StreamMetrics - Application Metrics\u0026rdquo;\nStep 5: Redis Metrics with Exporter #Redis doesn\u0026rsquo;t expose Prometheus metrics natively. We need a sidecar exporter. JVM Heap, CPU Usage Kafka Metrics HTTP Metrics Create k8s/monitoring/redis-exporter.yaml:\napiVersion: apps/v1 kind: Deployment metadata: name: redis-exporter namespace: streammetrics spec: replicas: 1 selector: matchLabels: app: redis-exporter template: metadata: labels: app: redis-exporter spec: containers: - name: redis-exporter image: oliver006/redis_exporter:latest ports: - containerPort: 9121 env: - name: REDIS_ADDR value: \u0026#34;redis:6379\u0026#34; --- apiVersion: v1 kind: Service metadata: name: redis-exporter namespace: streammetrics labels: app: redis-exporter spec: ports: - port: 9121 targetPort: 9121 name: metrics selector: app: redis-exporter --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: redis-monitor namespace: streammetrics labels: release: prometheus spec: selector: matchLabels: app: redis-exporter endpoints: - port: metrics interval: 30s Deploy:\nkubectl apply -f k8s/monitoring/redis-exporter.yaml # Wait kubectl wait --for=condition=ready pod -l app=redis-exporter -n streammetrics --timeout=60s Add Redis panels to dashboard:\nRedis Memory Usage:\nredis_memory_used_bytes{namespace=\u0026#34;streammetrics\u0026#34;} Redis Keys Count:\nredis_db_keys{namespace=\u0026#34;streammetrics\u0026#34;} Redis Operations/sec:\nrate(redis_commands_processed_total{namespace=\u0026#34;streammetrics\u0026#34;}[1m]) Step 6: Alerting - When Things Go Wrong #Dashboards are great for investigation, but you need alerts for production.\nCreate k8s/monitoring/alerts.yaml:\napiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: streammetrics-alerts namespace: streammetrics labels: release: prometheus spec: groups: - name: streammetrics interval: 30s rules: # Alert: Pod is down - alert: PodDown expr: up{namespace=\u0026#34;streammetrics\u0026#34;} == 0 for: 1m labels: severity: critical annotations: summary: \u0026#34;Pod {{ $labels.pod }} is down\u0026#34; description: \u0026#34;{{ $labels.pod }} in {{ $labels.namespace }} has been down for 1 minute\u0026#34; # Alert: High memory usage (\u0026gt;90%) - alert: HighMemoryUsage expr: (jvm_memory_used_bytes{area=\u0026#34;heap\u0026#34;,namespace=\u0026#34;streammetrics\u0026#34;} / jvm_memory_max_bytes{area=\u0026#34;heap\u0026#34;,namespace=\u0026#34;streammetrics\u0026#34;}) \u0026gt; 0.9 for: 5m labels: severity: warning annotations: summary: \u0026#34;{{ $labels.pod }} memory usage is high\u0026#34; description: \u0026#34;{{ $labels.pod }} is using {{ $value | humanizePercentage }} of heap memory\u0026#34; # Alert: High HTTP error rate - alert: HighErrorRate expr: rate(http_server_requests_seconds_count{status=~\u0026#34;5..\u0026#34;,namespace=\u0026#34;streammetrics\u0026#34;}[5m]) \u0026gt; 0.1 for: 5m labels: severity: critical annotations: summary: \u0026#34;High HTTP error rate on {{ $labels.uri }}\u0026#34; description: \u0026#34;{{ $labels.uri }} has {{ $value }} errors/sec\u0026#34; Apply:\nkubectl apply -f k8s/monitoring/alerts.yaml # View alerts in Prometheus open http://localhost:9090/alerts Alert states:\nInactive (green): Condition is false Pending (yellow): Condition true, waiting for for duration Firing (red): Alert is active, notifications sent Step 7: Testing the Monitoring #Generate Load ## Port-forward producer kubectl port-forward -n streammetrics svc/producer 8085:8085 \u0026amp; # Send 1000 events for i in {1..10}; do curl \u0026#34;http://localhost:8085/produce?events=100\u0026#34; sleep 1 done Watch the Dashboard Update #Open Grafana → StreamMetrics dashboard\nYou should see:\nCPU spike as producer sends events Memory increase in consumer HTTP requests/sec jump to 10 req/sec Kafka messages sent counter increasing HTTP request duration (p95) showing latency This is the magic moment - seeing your application\u0026rsquo;s behavior in real-time!\nProduction Lessons Learned #1. ServiceMonitors Must Match Helm Release #This error consumed 30 minutes:\nError: ServiceMonitor not discovered by Prometheus Root cause: ServiceMonitor labels didn\u0026rsquo;t match Prometheus selector.\nFix: Add labels.release: prometheus to match Helm release name.\n2. Imported Dashboards Break with Variables #Community dashboards use ${DS_PROMETHEUS} as a variable, but your datasource is named \u0026ldquo;Prometheus\u0026rdquo;.\nSolution: Create custom dashboards or fix the JSON manually.\n3. Spring Boot Kafka Metrics Are Limited #We expected kafka_consumer_fetch_manager_records_lag_max but got spring_kafka_template_seconds_*.\nWhy: Spring Boot\u0026rsquo;s Kafka integration exposes template metrics, not consumer internals.\nSolution: Use what\u0026rsquo;s available or add custom metrics with MeterRegistry.\n4. Redis Needs an Exporter #Unlike Spring Boot apps with /actuator/prometheus, Redis requires a separate exporter pod.\nPattern: For any non-Prometheus-native service (MySQL, MongoDB, etc.), use an exporter.\n5. PromQL is Powerful but Tricky #Wrong query:\njvm_memory_used_bytes # Shows all memory types Right query:\njvm_memory_used_bytes{area=\u0026#34;heap\u0026#34;, namespace=\u0026#34;streammetrics\u0026#34;} # Filtered Learn PromQL: It\u0026rsquo;s the key to unlocking insights.\nExporting and Sharing Dashboards #Export Dashboard #In Grafana:\nOpen dashboard Click \u0026ldquo;Share\u0026rdquo; (top right) Click \u0026ldquo;Export\u0026rdquo; tab Toggle \u0026ldquo;Export for sharing externally\u0026rdquo; ON Save to file Import Dashboard Later ## In Grafana UI Dashboards → Import → Upload JSON file # Or via API curl -X POST \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u admin:admin123 \\ http://localhost:3000/api/dashboards/db \\ -d @k8s/monitoring/dashboards/streammetrics-dashboard.json Monitoring Best Practices #1. Start with the Four Golden Signals #From Google\u0026rsquo;s SRE book:\nLatency: How long requests take Traffic: How many requests Errors: Rate of failed requests Saturation: How \u0026ldquo;full\u0026rdquo; your service is (CPU, memory) 2. Alert on Symptoms, Not Causes #Bad alert: \u0026ldquo;CPU usage \u0026gt; 80%\u0026rdquo;\nGood alert: \u0026ldquo;HTTP request p99 latency \u0026gt; 500ms\u0026rdquo;\nWhy? Users don\u0026rsquo;t care about CPU, they care about slow responses.\n3. Use Percentiles, Not Averages ## Average - hides outliers avg(http_server_requests_seconds_sum / http_server_requests_seconds_count) # P95 - shows what 95% of users experience histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) 4. Tag Everything #metrics: tags: application: streammetrics-producer environment: production region: us-west-2 version: v1.2.3 Makes filtering in PromQL trivial: {application=\u0026ldquo;streammetrics-producer\u0026rdquo;, environment=\u0026ldquo;production\u0026rdquo;}\n5. Keep Dashboards Simple #Bad dashboard: 50 panels, loads in 30 seconds\nGood dashboard: 10 key panels, loads instantly\nCreate multiple focused dashboards instead of one giant one.\nTry It Yourself #Full code on GitHub: https://github.com/ankitagrahari/StreamAnalytics\nUseful Commands ## Check if Prometheus is scraping targets kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090 open http://localhost:9090/targets # View alerts open http://localhost:9090/alerts # Check ServiceMonitors kubectl get servicemonitor -n streammetrics # View Prometheus config kubectl get prometheus -n monitoring -o yaml # Export dashboard via API curl -s http://admin:admin123@localhost:3000/api/dashboards/uid/\u0026lt;dashboard-uid\u0026gt; | jq \u0026#39;.dashboard\u0026#39; \u0026gt; dashboard.json Key Takeaways # Monitoring is not optional - Production without observability is gambling Prometheus Operator simplifies setup - ServiceMonitors \u0026gt; manual scrape configs Start with basics - JVM, HTTP, pod health; expand from there Custom dashboards \u0026gt; imported ones - You control the layout and queries Alert on what matters - User-facing symptoms, not internal metrics PromQL is worth learning - Powerful query language, steep but rewarding Export and version-control dashboards - Treat infrastructure as code Share your thoughts on whether you liked or disliked it. Let me know if you have any queries or suggestions.\nNever forget, Learning is the primary goal.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/production-monitoring-you-can-t-fix-what-you-can-t-see/","section":"Posts","summary":"\u003cp\u003ePrevious parts: \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-a-production-kafka-producer/\"\u003ePart 1: Kafka Producer\u003c/a\u003e | \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-production-grade-kafka-consumer-patterns/\"\u003ePart 2: Consumer + DLQ\u003c/a\u003e | \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/real-time-aggregations-with-kafka-streams-at-10k-events-sec/\"\u003ePart 3: Real-Time Aggregations\u003c/a\u003e | \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/from-localhost-to-kubernetes-deploying-streammetrics-at-scale/\"\u003ePart 4: Docker + Kubernetes\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eYou know that feeling when your app is running in production and someone asks \u0026ldquo;Is everything okay?\u0026rdquo; and you respond with \u0026ldquo;\u0026hellip;I think so?\u0026rdquo; Yeah, that\u0026rsquo;s not good enough.\u003c/p\u003e\n\u003cp\u003eAfter deploying StreamMetrics to Kubernetes with a 3-node KRaft Kafka cluster, dockerized microservices, and validated 10K events/sec throughput, I realized I was flying blind. Sure, the pods were running. The logs looked fine. But I had no idea:\u003c/p\u003e","title":"Production Monitoring: You Can't Fix What You Can't See"},{"content":"In today\u0026rsquo;s rapidly evolving tech landscape, the availability of diverse AI models for everyday users marks a significant shift in how we interact with technology. However, with this convenience comes a critical responsibility: ensuring data privacy remains intact. Enter Docker Desktop, a powerful tool that revolutionised application deployment by packaging large applications as lightweight images, which can be executed in isolated environments called containers.\nOne of the standout features of Docker Desktop is the Docker Model Runner. This innovative addition empowers developers to run AI models locally on their machines. By leveraging this feature, users can harness the capabilities of advanced AI without the need to share sensitive data or manage complex API keys. With Docker Model Runner, you can focus on development and experimentation while maintaining control over your data privacy.\nIn this blog post, we will explore how Docker Model Runner works, its benefits, and how it can enhance your AI development experience while safeguarding your data.\nDocker Model Runner #These are some important points about Docker Model Runner plugin:\nAvailable as Beta from v4.40 of Docker Desktop on Macbooks with Apple Silicon or Windows with Nvidia GPUs No Cost constraints. Useful for running, managing, and interacting with AI models directly from the command line. Models are pulled from Docker hub for first time which can take some time based on the size of the models, loads into memory only at runtime when a request is made, and unloaded when not in use. After the first time, these models are cached locally for faster access. You can use below OpenAI compatible APIs to interact with these models. # Docker Model management POST /models/create GET /models GET /models/{namespace}/{name} DELETE /models/{namespace}/{name} # OpenAI endpoints GET /engines/llama.cpp/v1/models GET /engines/llama.cpp/v1/models/{namespace}/{name} POST /engines/llama.cpp/v1/chat/completions POST /engines/llama.cpp/v1/completions POST /engines/llama.cpp/v1/embeddings Note: You can also omit llama.cpp. E.g., POST /engines/v1/chat/completions. Note: You can also remove llama.cpp from the endpoint\ne.g: GET /engines/v1/models/{namespace}/{name}\nEnabling Docker Model Runner # Your browser does not support the video tag. After enabling it, you will have to Apply and Restart.\nBe mindful of the model you are trying to pull and run. Since Docker desktop caches these models, it will effect the performance of your system, if the model is huge. It was advisable to have a small effective model to use. We are going to run Google Gemma 3.\nTo view all the available commands with Docker model runner, you can run\ndocker model help Available Commands Here we are pulling Google Gemma 3. When we run the model, it will run an interactive chat session like below: This will help a lot of developers in learning and growing with AI models. I will be using this in developing application which will interact with AI models without running the Ollama server as we did in the previous post https://www.dynamicallyblunttech.com/post/spring-ai-with-ollama.\nReference:\n-https://docs.docker.com/model-runner/\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/docker-desktop-model-runner/","section":"Posts","summary":"\u003cp\u003eIn today\u0026rsquo;s rapidly evolving tech landscape, the availability of diverse AI models for everyday users marks a significant shift in how we interact with technology. However, with this convenience comes a critical responsibility: \u003cem\u003eensuring data privacy remains intact\u003c/em\u003e. Enter Docker Desktop, a powerful tool that revolutionised application deployment by packaging large applications as lightweight images, which can be executed in isolated environments called containers.\u003c/p\u003e\n\u003cp\u003eOne of the standout features of Docker Desktop is the \u003cstrong\u003eDocker Model Runner\u003c/strong\u003e. This innovative addition empowers developers to run AI models locally on their machines. By leveraging this feature, users can harness the capabilities of advanced AI without the need to share sensitive data or manage complex API keys. With Docker Model Runner, you can focus on development and experimentation while maintaining control over your data privacy.\u003c/p\u003e","title":"Docker Desktop Model Runner"},{"content":"In this post we will look into how to change the swappiness value in Linux.\nSwappiness is the kernel parameter that defines how much your Linux kernel will use to copy RAM contents to swap. A low swap value means kernel will try to avoid swapping as much as possible, while a higher value will tend to use more data for swapping.\nApproach 1: #Steps # Login as root.\nTo get the current swappiness value:\ncat /proc/sys/vm/swappiness To edit, open /etc/sysctl.conf file and set the desired value for vm.swappiness\nYou can also set it like:\nsudo sysctl vm.swappiness =5 (can be between 0 to 100) After this it requires a restart to set the values. There is one more option without restarting your machine:\nApproach 2: #swapoff -a sudo sysctl vm.swappiness =5 swapon -a This will set the swappiness on Linux machine without any restart.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/set-swappiness-in-linux/","section":"Posts","summary":"\u003cp\u003eIn this post we will look into how to change the swappiness value in Linux.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSwappiness\u003c/strong\u003e is the kernel parameter that defines how much your Linux kernel will use to copy RAM contents to swap. A low swap value means kernel will try to avoid swapping as much as possible, while a higher value will tend to use more data for swapping.\u003c/p\u003e\n\u003ch3 id=\"approach-1\" class=\"relative group\"\u003eApproach 1: \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#approach-1\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003ch4 id=\"steps\" class=\"relative group\"\u003eSteps \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#steps\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eLogin as root.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eTo get the current swappiness value:\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e","title":"Set Swappiness in Linux"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/aipipeline/","section":"Tags","summary":"","title":"AIPipeline"},{"content":" TL;DR — CodeForgeAI is a Spring Boot + Vaadin application that orchestrates five specialised AI agents (Business Analyst → Code Generator → Code Reviewer → Test Generator → Test Executor) to transform a PDF requirements document into reviewed, tested, and deployed Java code — all running on-premise on a developer laptop, with no cloud LLM calls, no data leaving the machine.\nTable of Contents # Motivation \u0026amp; Goals Tech Stack End-to-End Pipeline Architecture The 5 Agents — Deep Dive Business Analyst Agent Code Generator Agent Code Review Agent Test Generator Agent Test Executor Agent RAG: Codebase Ingestion \u0026amp; Vector Store The Hardest Part: Running Multiple LLMs Locally via Ollama Hardware Constraints Models We Evaluated The num_ctx Uniformity Breakthrough Final Model Decision Ollama Environment Variables That Mattered Challenges We Solved Code Structure Real-Time UI with Vaadin \u0026amp; Server Push Jira Integration Human-in-the-Loop Checkpoints What We Learned 1. Motivation \u0026amp; Goals #Generative AI is reshaping software development, but most AI coding tools are cloud-hosted, meaning proprietary code is sent to external servers. For enterprise environments where source code must never leave the network, that\u0026rsquo;s a hard blocker.\nWe set out to answer a specific question: Can a fully local, open-weight LLM pipeline produce production-quality Java code, tests, and Jira stories from a requirements PDF — without a single API call to a cloud provider?\nThe constraints were deliberate:\n100% on-premise — Ollama on a developer laptop, no external model APIs. Agentic pipeline — not a single mega-prompt, but a chain of specialised agents, each with its own role, prompt, and review loop. Real codebase awareness — the pipeline reads the existing target module\u0026rsquo;s source, ingests it into a vector store, and generates code that fits the existing structure. Human review gates — the pipeline pauses at key stages so an engineer can approve before code is written to disk. 2. Tech Stack # Layer Technology Backend framework Spring Boot 3.4, Java 21 AI integration Spring AI (Ollama backend) Local LLM runtime Ollama 0.6 Vector store PostgreSQL + PGVector (HNSW index, cosine distance) Embedding model nomic-embed-text (768-dim) Primary LLM qwen2.5-coder:7b-instruct (Q4_K_M, ~4.7 GB) Frontend Vaadin Flow 24 (server-side Java UI, Lumo theme) Database PostgreSQL 16 Connection pool HikariCP (keepalive-tuned for long LLM calls) Issue tracking Jira Cloud REST API v3 Build Maven 3 3. End-to-End Pipeline Architecture # End to End Pipeline Architecture Status State Machine # State Machine 4. The 5 Agents — Deep Dive #4.1 Business Analyst Agent #The BA agent reads the raw requirements text and returns a structured JSON array of user stories, each with a title, description in \u0026ldquo;As a / I want / So that\u0026rdquo; format, acceptance criteria, and test case specs.\nThe core challenge was that small 7B models regularly wrap JSON in markdown fences, add preamble text like \u0026ldquo;Sure, here are your user stories:\u0026rdquo;, or append trailing commentary. We built a four-layer extraction strategy:\n// From: BusinessAnalystAgent.java private String extractJson(String response) { // Layer 1: markdown-fenced JSON ─ ```json [...] ``` Matcher markdownMatcher = MARKDOWN_JSON_PATTERN .matcher(response); if (markdownMatcher.find()) { return markdownMatcher.group(1).trim(); } // Layer 2: bare JSON array anywhere in the text Matcher jsonMatcher = JSON_ARRAY_PATTERN.matcher(response); if (jsonMatcher.find()) { return jsonMatcher.group().trim(); } // Layer 3: whole response is already a JSON array String trimmed = response.trim(); if (trimmed.startsWith(\u0026#34;[\u0026#34;) \u0026amp;\u0026amp; trimmed.endsWith(\u0026#34;]\u0026#34;)) { return trimmed; } // Layer 4: bracket-matching fallback — find first \u0026#39;[\u0026#39; and last \u0026#39;]\u0026#39; int firstBracket = trimmed.indexOf(\u0026#39;[\u0026#39;); int lastBracket = trimmed.lastIndexOf(\u0026#39;]\u0026#39;); if (firstBracket \u0026gt;= 0 \u0026amp;\u0026amp; lastBracket \u0026gt; firstBracket) { String candidate = trimmed.substring(firstBracket, lastBracket + 1); if (candidate.contains(\u0026#34;{\u0026#34;)) return candidate; } return null; } On parsing failure the agent retries with a strengthened prompt:\n// From: BusinessAnalystAgent.java — retry reinforcement if (attempt \u0026gt; 0) { prompt = prompt + \u0026#34;\\n\\nREMINDER: You MUST output ONLY a JSON array starting \u0026#34; + \u0026#34;with [ and ending with ]. Do NOT say anything else. \u0026#34; + \u0026#34;Do NOT introduce yourself. Just output the JSON array of user stories.\u0026#34;; } BA prompt template (ba-user-story-generator.st):\nAnalyze the following software requirements document and generate User Stories. === START OF REQUIREMENTS DOCUMENT === {requirements} === END OF REQUIREMENTS DOCUMENT === Based on the requirements above, generate a complete set of User Stories. Each story must have a title, description (As a... I want... So that...), acceptance criteria, and test cases. You MUST respond with ONLY a JSON array. No other text, no explanations, no markdown fences. The JSON array must use this exact structure: [ { \u0026#34;title\u0026#34;: \u0026#34;Short descriptive title\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;As a [role], I want [feature], so that [benefit].\u0026#34;, \u0026#34;acceptanceCriteria\u0026#34;: [ \u0026#34;Given [context], When [action], Then [outcome]\u0026#34; ], \u0026#34;testCases\u0026#34;: [ { \u0026#34;title\u0026#34;: \u0026#34;Test case title\u0026#34;, \u0026#34;steps\u0026#34;: \u0026#34;1. Step one\\n2. Step two\u0026#34;, \u0026#34;expectedResult\u0026#34;: \u0026#34;Expected outcome description\u0026#34; } ] } ] 4.2 Code Generator Agent #The code generator is the most complex agent. It runs in three distinct modes:\nMode 1: Planning Phase #Before any code is generated, a lightweight LLM call produces a file plan — a numbered list of every file that needs to be created or modified across all stories. This prevents cross-story inconsistencies like two stories each creating a BookService.java that overwrite each other.\n// From: CodeGeneratorAgent.java public void runPlanningPhase(PipelineContext context) { String prompt = planPromptTemplate .replace(\u0026#34;{projectStructure}\u0026#34;, context.getProjectStructure()) .replace(\u0026#34;{existingSourceSummary}\u0026#34;, context.getExistingSourceSummary()) .replace(\u0026#34;{requirements}\u0026#34;, context.getOriginalRequirements()) .replace(\u0026#34;{userStories}\u0026#34;, context.getUserStoriesJson()); String planOutput = chatClient.prompt().user(prompt).call().content(); context.setGenerationPlan(planOutput.trim()); } Mode 2: Per-Story Code Generation (the Signatures-Index Trick) #Instead of passing full accumulated code between stories (which blows past the 16,384-token context window after just 2–3 stories), we pass only method signatures — a condensed index showing class names, field declarations, and method signatures without bodies:\n// From: CodeGeneratorAgent.java public String buildAccumulatedCodeIndex(Map\u0026lt;String, String\u0026gt; fileMap) { StringBuilder sb = new StringBuilder(); for (Map.Entry\u0026lt;String, String\u0026gt; entry : fileMap.entrySet()) { sb.append(\u0026#34;// === FILE: \u0026#34;).append(entry.getKey()).append(\u0026#34; ===\\n\u0026#34;); String content = entry.getValue(); // Include: package declaration Matcher pkgMatcher = PACKAGE_DECLARATION.matcher(content); if (pkgMatcher.find()) sb.append(pkgMatcher.group()).append(\u0026#34;\\n\u0026#34;); // Include: class/interface declaration line only Matcher classMatcher = CLASS_NAME_EXTRACT.matcher(content); if (classMatcher.find()) { int classStart = classMatcher.start(); int lineStart = content.lastIndexOf(\u0026#39;\\n\u0026#39;, classStart) + 1; int lineEnd = content.indexOf(\u0026#39;\\n\u0026#39;, classStart); String classLine = content.substring(lineStart, lineEnd \u0026lt; 0 ? content.length() : lineEnd).trim(); sb.append(classLine.replaceAll(\u0026#34;\\\\{.*$\u0026#34;, \u0026#34;{\u0026#34;)).append(\u0026#34;\\n\u0026#34;); } // Include: public/protected method signatures only (no bodies) Matcher methodMatcher = PUBLIC_METHOD_PATTERN.matcher(content); while (methodMatcher.find()) { String sig = methodMatcher.group().trim().replaceAll(\u0026#34;\\\\{\\\\s*$\u0026#34;, \u0026#34;;\u0026#34;); sb.append(\u0026#34; \u0026#34;).append(sig).append(\u0026#34;\\n\u0026#34;); } sb.append(\u0026#34;}\\n\u0026#34;); } return sb.toString(); } This reduces a 4,000-line accumulated codebase to ~200 lines of signatures, keeping every per-story prompt well within budget.\nMode 3: Feedback Loop Regeneration #When the AI code reviewer rejects a story\u0026rsquo;s code, the generator gets a focused regeneration prompt containing the previous code + the specific issues found:\n// From: CodeGeneratorAgent.java public String regenerateStoryWithFeedback( PipelineContext context, UserStory story, String previousStoryCode, String feedback, Map\u0026lt;String, String\u0026gt; accumulatedFiles) { // Per-story feedback: no truncation needed since context is small (single story) String prompt = perStoryFeedbackPromptTemplate .replace(\u0026#34;{previousStoryCode}\u0026#34;, previousStoryCode) .replace(\u0026#34;{feedback}\u0026#34;, feedback) ...; log.info(\u0026#34;Per-story feedback prompt size: {} chars (no truncation needed).\u0026#34;, prompt.length()); return extractCode(chatClient.prompt().user(prompt).call().content()); } Handling Truncated Output #7B models regularly hit their num_predict token limit mid-method, producing code with more { than }. We detect and auto-repair:\n// From: CodeGeneratorAgent.java long openBraces = code.chars().filter(c -\u0026gt; c == \u0026#39;{\u0026#39;).count(); long closeBraces = code.chars().filter(c -\u0026gt; c == \u0026#39;}\u0026#39;).count(); if (openBraces \u0026gt; closeBraces) { log.warn(\u0026#34;Generated code appears TRUNCATED: {} open braces, {} close braces.\u0026#34;, openBraces, closeBraces); StringBuilder sb = new StringBuilder(code); for (long i = 0; i \u0026lt; openBraces - closeBraces; i++) sb.append(\u0026#34;\\n}\u0026#34;); code = sb.toString(); log.info(\u0026#34;Auto-closed {} missing braces.\u0026#34;, openBraces - closeBraces); } The per-story prompt template enforces strict output format:\nCRITICAL OUTPUT FORMAT — READ CAREFULLY: Your ENTIRE response must be a single ```java code block containing ALL files. Do NOT write ANY text, commentary, analysis, or explanations outside the code block. Start your response IMMEDIATELY with ```java and end with ```. If you write plain text instead of code, you have FAILED the task. 4.3 Code Review Agent #The reviewer runs a two-layer check: first a deterministic structural pre-validation, then an LLM-powered semantic review.\nStructural pre-validation catches the most common generator failures before invoking the (expensive) LLM call:\n// From: CodeReviewAgent.java private List\u0026lt;String\u0026gt; validateStructure(String code) { List\u0026lt;String\u0026gt; issues = new ArrayList\u0026lt;\u0026gt;(); if (!code.contains(\u0026#34;// === FILE:\u0026#34;)) { issues.add(\u0026#34;No FILE separators found. The code generator must separate each \u0026#34; + \u0026#34;class with a FILE separator for proper deployment.\u0026#34;); } if (!code.contains(\u0026#34;package \u0026#34;)) { issues.add(\u0026#34;No package declarations found. Every Java file must have a package.\u0026#34;); } long openBraces = code.chars().filter(c -\u0026gt; c == \u0026#39;{\u0026#39;).count(); long closeBraces = code.chars().filter(c -\u0026gt; c == \u0026#39;}\u0026#39;).count(); if (openBraces != closeBraces) { issues.add(String.format(\u0026#34;Unbalanced braces: %d open \u0026#39;{\u0026#39; vs %d close \u0026#39;}\u0026#39;.\u0026#34;, openBraces, closeBraces)); } if (!code.contains(\u0026#34;import \u0026#34;)) { issues.add(\u0026#34;No import statements found.\u0026#34;); } return issues; } The LLM is asked to return a structured JSON verdict:\n{ \u0026#34;approved\u0026#34;: false, \u0026#34;issues\u0026#34;: [\u0026#34;Missing @RestController annotation\u0026#34;, \u0026#34;findById returns raw Optional\u0026#34;], \u0026#34;suggestions\u0026#34;: \u0026#34;Add proper ResponseEntity wrapping and null handling.\u0026#34; } We use a brace-counting JSON extractor (not regex) to reliably parse nested JSON from noisy LLM output:\n// From: CodeReviewAgent.java private String extractJson(String text) { Matcher startMatcher = JSON_START_PATTERN.matcher(text); // looks for {\u0026#34;approved\u0026#34; if (!startMatcher.find()) return null; int start = startMatcher.start(); int depth = 0; boolean inString = false, escaped = false; for (int i = start; i \u0026lt; text.length(); i++) { char c = text.charAt(i); if (escaped) { escaped = false; continue; } if (c == \u0026#39;\\\\\u0026#39; \u0026amp;\u0026amp; inString) { escaped = true; continue; } if (c == \u0026#39;\u0026#34;\u0026#39;) { inString = !inString; continue; } if (!inString) { if (c == \u0026#39;{\u0026#39;) depth++; else if (c == \u0026#39;}\u0026#39;) { depth--; if (depth == 0) return text.substring(start, i + 1); } } } return null; } Safety-first default: if JSON parsing fails entirely, the reviewer defaults to approved = false — preventing bad code from being deployed.\n4.4 Test Generator Agent #For each story, the test generator receives the generated Java code, the user story with its BA-authored test case specs, and the project structure. It produces JUnit 5 tests with AssertJ assertions and Mockito mocking, placed in the correct test package.\n// From: TestGeneratorAgent.java — per-story prompt String prompt = \u0026#34;\u0026#34;\u0026#34; Generate comprehensive JUnit 5 unit tests for the following Java source code. === PROJECT STRUCTURE === %s === SOURCE CODE === %s === USER STORY REQUIREMENTS === %s Generate tests that verify the code fulfills the user story requirements. Place test classes in the appropriate test package matching the source package structure. Include edge cases, boundary values, and error scenarios. Use AssertJ assertions and Mockito for mocking where appropriate. \u0026#34;\u0026#34;\u0026#34;.formatted(projectStructure, storyCode, requirements.toString()); 4.5 Test Executor Agent #The Test Executor compiles all accumulated code and tests together (using cross-story dependencies) in a temporary directory and runs the tests via the Maven Surefire plugin. It captures stdout/stderr and parses pass/fail counts:\n// From: PipelineOrchestrator.java — per-story test execution String allCode = codeGeneratorAgent.buildAccumulatedCodeString(accumulatedFiles); TestExecutionResult testResult = testExecutorAgent.executeForStory(allCode, result.getGeneratedTests()); result.setCompilationSuccess(testResult.compiled()); result.setTestsRun(testResult.testsRun()); result.setTestsPassedCount(testResult.testsPassed()); result.setTestsFailedCount(testResult.testsFailed()); 5. RAG: Codebase Ingestion \u0026amp; Vector Store #Before any code generation begins, the target module\u0026rsquo;s entire source tree is ingested into PGVector using nomic-embed-text embeddings. This gives the Code Generator real context about the existing architecture.\nFingerprint-based cache — re-ingestion only happens when files actually change:\n// From: CodebaseIngestionService.java private String computeFingerprint(List\u0026lt;Path\u0026gt; files) { StringBuilder sb = new StringBuilder(); for (Path file : files) { sb.append(file.toAbsolutePath()) .append(\u0026#39;:\u0026#39;) .append(Files.getLastModifiedTime(file).toMillis()) .append(\u0026#39;;\u0026#39;); } MessageDigest digest = MessageDigest.getInstance(\u0026#34;SHA-256\u0026#34;); byte[] hash = digest.digest(sb.toString().getBytes(StandardCharsets.UTF_8)); return HexFormat.of().formatHex(hash).substring(0, 16); } Two context modes — for small codebases (\u0026lt;10 KB total source), the full file content is passed. For larger codebases, we extract signatures only:\n// From: CodebaseIngestionService.java boolean fullMode = totalChars \u0026lt; 10_000; log.info(\u0026#34;Building source summary for {} Java files ({}KB total, mode={})\u0026#34;, javaFiles.size(), totalChars / 1024, fullMode ? \u0026#34;FULL\u0026#34; : \u0026#34;SIGNATURES\u0026#34;); Chunking with TokenTextSplitter:\nTokenTextSplitter splitter = TokenTextSplitter.builder() .withChunkSize(800) .withMinChunkSizeChars(100) .withMinChunkLengthToEmbed(50) .withMaxNumChunks(200) .withKeepSeparator(true) .build(); RAG retrieval is configured in AiConfig.java:\n// From: AiConfig.java — only the Code Generator uses RAG .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder().topK(8).build()) .build()) The Code Review and BA agents deliberately do not use RAG — the review agent needs to be objective about the generated code, and injecting random source embeddings into the BA agent\u0026rsquo;s prompt confused the model.\n6. The Hardest Part: Running Multiple LLMs Locally via Ollama #This section is the heart of the project. Nothing in the design took more time or produced more surprising failures than the local LLM configuration.\n6.1 Hardware Constraints #The development machine is an Intel Core Ultra 7 265H with 32 GB RAM. No discrete GPU. Intel Arc Xe is present but Ollama\u0026rsquo;s llama.cpp backend has no Intel GPU path — everything runs on CPU via AVX2 + AVX_VNNI. That gives roughly 8–12 tokens/second for a 7B Q4_K_M model and 20–30 tokens/second for a 2B model.\nA full pipeline run with 4 user stories takes 15–25 minutes on this hardware. That is the price of 100% on-premise.\n6.2 Models We Evaluated #We tested six models over the course of the project. The comparison shaped every configuration decision.\nModel Size Speed (tok/s) Code Quality Decision gemma4:e2b ~2 GB 20–30 ❌ Truncates multi-file output, misses imports BA only gemma4:e4b ~9.4 GB 4–7 ⚠️ General-purpose, not code-specialised Rejected deepseek-coder-v2:16b ~10 GB 3–5 ✅ Best code quality Too slow \u0026amp; too large qwen2.5-coder:7b-instruct ~4.7 GB 8–12 ✅ Best balance SELECTED ✓ nomic-embed-text ~0.5 GB n/a Embedding only SELECTED ✓ From the actual Modelfile comments:\n# Modelfile.qwen-coder # # COMPARISON vs alternatives: # gemma4:e2b (~7.2 GB) → 20-30 tok/sec BUT truncates multi-file output, # misses imports → USE FOR BA ONLY # gemma4:e4b (~9.4 GB) → 4-7 tok/sec, general-purpose, not code-specialised # deepseek-coder-v2:16b → best code quality, ~3-5 tok/sec # (use if quality \u0026gt; speed) # qwen-coder:7b (THIS) → best balance of speed + code quality ✓ Why gemma4:e2b failed for code generation:\nWe ran gemma4:e2b on the full code generation pipeline first because its 2B size meant near-instant responses. It failed consistently in three ways: it would stop generating mid-class leaving unbalanced braces, it skipped all import statements (the Jakarta/JPA/Lombok imports that Spring Boot relies on), and it never produced // === FILE: === separators — the convention our pipeline uses to split multi-class output into individual files for deployment. The model simply lacked the depth to hold an entire Spring Boot class hierarchy in its working memory.\nWhy deepseek-coder-v2:16b was not selected (despite best quality):\nThe code quality was genuinely impressive — better package placement, better REST conventions, more idiomatic Spring patterns. But at 10 GB weight + ~3 tok/sec on this CPU, a single per-story code generation call took 8–12 minutes. A 4-story pipeline would take over an hour. Additionally, we hit a subtle double-BOS (beginning-of-sentence) token bug in Ollama\u0026rsquo;s chat template — documented in Modelfile.deepseek-coder:\n# Fixed Modelfile for deepseek-coder-v2:16b # Change: Removed explicit BOS token from the chat template. # The llama.cpp tokenizer already adds BOS via add_bos_token=true in the model config. # Having it in the template too causes the double-BOS warning and degrades generation quality. This required creating a custom Modelfile to strip the duplicated token.\n6.3 The num_ctx Uniformity Breakthrough #The single most impactful performance discovery of the project: Ollama reloads the entire model from disk whenever num_ctx changes between requests. On a 4.7 GB model over a 20-second cold load, this adds 20–30 seconds to every pipeline stage transition.\nEarly in development, we had different context windows: BA at 8192, Code Generator at 16384, Code Reviewer at 8192, Test Generator at 12288. Every agent-to-agent handoff forced a model reload. A 4-story pipeline was losing 2–3 minutes to reloads alone.\nThe fix was to normalise every agent to num_ctx = 16384:\n# application.yaml codeforgeai: ollama: # Uniform 16384 context window for all agents — avoids Ollama model reloads code-num-ctx: 16384 review-num-ctx: 16384 test-num-ctx: 16384 ba-num-ctx: 16384 # Output limits stay per-agent (these don\u0026#39;t cause model reloads) code-num-predict: 6144 review-num-predict: 1024 test-num-predict: 6144 ba-num-predict: 2048 Memory math for num_ctx = 16384 at Q4_K_M:\nKV cache per token = 2 × num_layers × head_dim × bytes_per_element qwen2.5-coder:7b at 16384 tokens → KV cache ≈ 0.6 GiB ✓ trivial on 32 GB Total memory budget:\nqwen2.5-coder:7b weights (Q4_K_M) : ~4.7 GiB KV cache (num_ctx=16384) : ~0.6 GiB Compute graph overhead : ~0.3 GiB nomic-embed-text (always-on) : ~0.5 GiB Spring Boot JVM : ~1.5 GiB Windows OS overhead : ~6.0 GiB ───────────────────────────────────────────── Total : ~13.6 GiB of 32 GiB ✓ comfortable Free headroom : ~18 GiB 6.4 Final Model Decision #One model (qwen2.5-coder:7b-instruct) running all 4 code/review/test agents, uniform num_ctx = 16384. No hot-swap delays. No model reloads between pipeline stages.\nPer-agent temperature was tuned separately:\n// From: AiConfig.java // Code Generator — slightly creative but predictable OllamaChatOptions.builder().temperature(0.3)... // Code Reviewer — consistent, reproducible JSON verdicts OllamaChatOptions.builder().temperature(0.2)... // Test Generator — slightly creative for edge case discovery OllamaChatOptions.builder().temperature(0.3)... // BA Agent — deterministic JSON output OllamaChatOptions.builder().temperature(0.2)... The Modelfile pins temperature to 0.1 at the model level (overridable per-request by the app):\n# Modelfile.qwen-coder PARAMETER temperature 0.1 # Very low: deterministic, reproducible code PARAMETER num_thread 16 # All 16 threads — AVX2 + AVX_VNNI fast path PARAMETER num_ctx 16384 PARAMETER num_predict 6144 PARAMETER stop \u0026lt;|im_end|\u0026gt; PARAMETER stop \u0026lt;|endoftext|\u0026gt; 6.5 Ollama Environment Variables That Mattered ## Set permanently in Windows System Environment Variables OLLAMA_MAX_LOADED_MODELS = 1 # One model hot at a time (single model, no swap) OLLAMA_NUM_PARALLEL = 1 # One inference at a time (CPU can\u0026#39;t parallelise) OLLAMA_FLASH_ATTENTION = 1 # Reduces KV cache memory usage OLLAMA_KEEP_ALIVE = 10m # Keep model loaded during pipeline, unload after OLLAMA_FLASH_ATTENTION=1 alone reduced KV cache by ~30% with no measurable quality impact — worth setting on any CPU-bound deployment.\n7. Challenges We Solved #Challenge 1: LLM Ignores Output Format Instructions #Problem: Even with explicit \u0026ldquo;respond with only a JSON array\u0026rdquo; instructions, qwen2.5-coder would prefix responses with \u0026ldquo;Sure! Here are your user stories:\u0026rdquo; or wrap output in markdown fences.\nSolution: Multi-layer extraction (see §4.1) + retry with reinforcement prompt. On second attempt, we append a stern reminder directly to the prompt.\nChallenge 2: Context Window Overflow on Multi-Story Pipelines #Problem: Story 1 generates BookController.java, BookService.java, and BookRepository.java. Story 2 needs to know about all three to avoid duplicate code, but passing them verbatim consumed 60–70% of the context window before the story description even started.\nSolution: The signatures-only index (buildAccumulatedCodeIndex) — pass method signatures only, not method bodies. A 400-line Java class reduces to ~15 lines of signatures.\nChallenge 3: Truncated Output — Unclosed Braces #Problem: At num_predict = 6144, the model would hit the token limit mid-method:\npublic List\u0026lt;Book\u0026gt; searchByTitle(String title) { return bookRepository.findByTitleContainingIgnoreCase(title); // \u0026lt;token limit hit — class never closed\u0026gt; Solution: Detect truncated code blocks (UNCLOSED_CODE_BLOCK_PATTERN), count { vs }, and auto-close the deficit:\nMatcher unclosedMatcher = UNCLOSED_CODE_BLOCK_PATTERN.matcher(response); if (unclosedMatcher.find()) { String truncatedCode = unclosedMatcher.group(1).trim(); log.warn(\u0026#34;Detected TRUNCATED code output. Length: {} chars\u0026#34;, truncatedCode.length()); codeBlocks.add(truncatedCode); } Challenge 4: PostgreSQL \u0026ldquo;Marked as Broken\u0026rdquo; Connections #Problem: LLM inference calls run for 3–8 minutes. During that time, HikariCP connections sit idle. PostgreSQL silently drops idle TCP connections after ~5 minutes, leaving Hikari\u0026rsquo;s pool holding dead connections. The next DB write fails with SQLSTATE(08006).\nSolution: Two-pronged fix in application.yaml:\nhikari: keepalive-time: 30000 # 30s — sends \u0026#34;SELECT 1\u0026#34; to PG every 30s idle-timeout: 600000 # 10 min — return connections before PG drops them connection-timeout: 30000 leak-detection-threshold: 120000 # Warn if connection held \u0026gt; 2 min Combined with TCP keepalives in the JDBC URL:\nurl: jdbc:postgresql://localhost:5432/codeforgeai?tcpKeepAlive=true\u0026amp;socketTimeout=0\u0026amp;connectTimeout=10 Challenge 5: deepseek-coder-v2 Double-BOS Token #Problem: Ollama\u0026rsquo;s default chat template for deepseek-coder-v2:16b injected an explicit \u0026lt;｜begin▁of▁sentence｜\u0026gt; token, but llama.cpp also adds it via add_bos_token=true in the model config. The double-BOS produced garbled generation quality.\nSolution: Custom Modelfile stripping the explicit BOS from the template (Modelfile.deepseek-coder).\nChallenge 6: Ollama Model Reloads Between Pipeline Stages #Already described in §6.3. The fix was normalising num_ctx = 16384 across all agents.\nChallenge 7: LLM Returns Plain Text Instead of Code #The code generator has a retry path for this:\n// From: CodeGeneratorAgent.java log.warn(\u0026#34;LLM returned plain text. Attempting retry with explicit code-only instruction...\u0026#34;); String retryPrompt = \u0026#34;Your previous response was plain text, NOT code. \u0026#34; + \u0026#34;Convert the following description into actual Java source code. \u0026#34; + \u0026#34;Respond with ONLY a ```java code block. \u0026#34; + \u0026#34;No explanations. No commentary. Just code. \u0026#34; + \u0026#34;Start EVERY file with: // === FILE: \u0026lt;relative-path-from-src\u0026gt; ===\\n\u0026#34; + \u0026#34;Start immediately with ```java\\n\\n\u0026#34; + \u0026#34;Here is what you described (convert this to code):\\n\u0026#34; + response.substring(0, Math.min(4000, response.length())); 8. Code Structure #codeforgeai/ ├── src/main/java/org/epam/codeforgeai/ │ ├── agent/ # The 5 AI agents │ │ ├── Agent.java # Common interface: execute(PipelineContext) │ │ ├── BusinessAnalystAgent.java # Requirements → User Stories JSON │ │ ├── CodeGeneratorAgent.java # User Stories → Java source code (839 lines) │ │ ├── CodeReviewAgent.java # Java code → JSON review verdict │ │ ├── TestGeneratorAgent.java # Java code → JUnit 5 tests │ │ └── TestExecutorAgent.java # Compile + run tests, capture results │ │ │ ├── config/ │ │ ├── AiConfig.java # 4 ChatClient beans (per-agent model/temp/ctx) │ │ ├── AppProperties.java # @ConfigurationProperties for YAML │ │ ├── AsyncConfig.java # @Async thread pool for pipeline │ │ └── VaadinConfig.java # Vaadin push + session config │ │ │ ├── event/ │ │ └── PipelineProgressEvent.java # Spring ApplicationEvent for UI push │ │ │ ├── model/ │ │ ├── dto/ │ │ │ ├── PipelineContext.java # Mutable pipeline state (passed agent→agent) │ │ │ ├── UserStory.java # BA output: title, description, AC, test cases │ │ │ ├── StoryPipelineResult.java# Per-story: code, review, tests, exec output │ │ │ ├── CodeReviewResult.java # approved, issues[], suggestions │ │ │ ├── AgentToggles.java # Feature flags: enableBA, enableDev, etc. │ │ │ └── TestCaseSpec.java # Test case: title, steps, expectedResult │ │ ├── entity/ │ │ │ ├── PipelineRun.java # JPA: pipeline run with all results │ │ │ └── AgentExecution.java # JPA: per-agent timing + status │ │ └── enums/ │ │ ├── PipelineStatus.java # PENDING→IN_PROGRESS→...→COMPLETED │ │ └── AgentType.java # BA_AGENT, CODE_GENERATOR, etc. │ │ │ ├── repository/ │ │ ├── PipelineRunRepository.java # JPA + custom queries │ │ └── [AgentExecutionRepository.java](http://AgentExecutionRepository.java) │ │ │ ├── service/ │ │ ├── PipelineOrchestrator.java # @Async: coordinates all 5 agents (1053 lines) │ │ ├── PipelineTrackingService.java# Transactional DB writes for pipeline state │ │ ├── CodebaseIngestionService.java # RAG: walks module, chunks, embeds to PGVector │ │ ├── CodeDeploymentService.java # Writes approved Java files to module/src/ │ │ ├── DocumentIngestionService.java # PDF/TXT → raw requirements text │ │ └── JiraService.java # Creates Jira stories + test cases via REST │ │ │ └── ui/ # Vaadin views │ ├── MainLayout.java # AppLayout: sidebar nav + dark mode toggle │ ├── NewPipelineView.java # Upload PDF, set agent toggles, start pipeline │ ├── PipelineDetailView.java # Real-time pipeline progress (1151 lines) │ ├── PipelineHistoryView.java # History grid with results │ ├── BaPipelineView.java # BA-only analysis view │ └── DashboardView.java # Stats: total runs, pass rate, etc. │ ├── src/main/resources/ │ ├── application.yaml # All config: Ollama, Hikari, PGVector, Jira │ └── prompts/ │ ├── ba-system.st # BA system prompt │ ├── ba-user-story-generator.st # BA user prompt template │ ├── code-generator-system.st # Code gen system prompt │ ├── code-generator-plan.st # Planning phase prompt │ ├── code-generator-per-story.st # Per-story generation prompt │ ├── code-generator-per-story-feedback.st # Per-story feedback regen │ ├── code-generator-with-feedback.st # Legacy monolithic feedback │ ├── code-review-system.st # Code review system prompt │ └── test-generator-system.st # Test generator system prompt │ └── docker/ ├── Modelfile.qwen-coder # qwen2.5-coder:7b CPU config ├── Modelfile.gemma4 # gemma4:e2b BA-agent config └── Modelfile.deepseek-coder # deepseek-coder-v2:16b (alternative) 9. Real-Time UI with Vaadin \u0026amp; Server Push #The UI is built with Vaadin Flow (server-side Java), rendering all components in Java without writing HTML or JavaScript.\nServer Push architecture: The pipeline runs in a background thread (@Async). To update the browser in real time, each agent call publishes a PipelineProgressEvent using Spring\u0026rsquo;s ApplicationEventPublisher. A static registry in PipelineDetailView maps pipeline run IDs to active browser sessions:\n// From: PipelineDetailView.java private static final Map\u0026lt;UUID, Set\u0026lt;PipelineDetailView\u0026gt;\u0026gt; ACTIVE_VIEWS = new ConcurrentHashMap\u0026lt;\u0026gt;(); @EventListener public static void onPipelineProgress(PipelineProgressEvent event) { Set\u0026lt;PipelineDetailView\u0026gt; views = ACTIVE_VIEWS.get(event.getPipelineRunId()); if (views == null || views.isEmpty()) return; for (PipelineDetailView view : views) { if (view.ui != null) { view.ui.access(() -\u0026gt; { // UI.access() → server push to browser view.updateStepCard(event.getAgentType(), event.getStatus(), event.getMessage()); view.updateStatusBadge(event.getStatus()); view.updateProgressBar(event.getStatus()); view.loadPipelineData(); // Refresh per-story sections from DB }); } } } Progress bar is computed from real story/agent data — not a spinner:\n// From: PipelineDetailView.java // Layout: BA (10%) → Stories (80%) → Final (10%) // Within each story: code=25% → review=50% → test gen=75% → test exec=100% double storyWeight = 0.80 / lastKnownTotalStories; double phaseProgress = switch (lastKnownAgentType) { case CODE_GENERATOR -\u0026gt; 0.25; case CODE_REVIEWER -\u0026gt; 0.50; case TEST_GENERATOR -\u0026gt; 0.75; case TEST_EXECUTOR -\u0026gt; 1.0; default -\u0026gt; 0.0; }; double progress = 0.10 + (lastKnownStoryIndex * storyWeight) + (phaseProgress * storyWeight); Dark mode toggle uses Vaadin\u0026rsquo;s Lumo theme with VaadinSession persistence:\n// From: MainLayout.java darkModeBtn.addClickListener(e -\u0026gt; { var themeList = UI.getCurrent().getElement().getThemeList(); boolean nowDark = themeList.contains(Lumo.DARK); if (nowDark) { themeList.remove(Lumo.DARK); darkModeBtn.setText(\u0026#34;🌙\u0026#34;); VaadinSession.getCurrent().setAttribute(DARK_MODE_SESSION_KEY, false); } else { themeList.add(Lumo.DARK); darkModeBtn.setText(\u0026#34;☀️\u0026#34;); VaadinSession.getCurrent().setAttribute(DARK_MODE_SESSION_KEY, true); } }); 10. Jira Integration #After the BA agent generates user stories, JiraService creates Jira issues and links test cases. The pipeline pauses at a human review checkpoint so the engineer can review the created stories in Jira before code generation begins.\n// From: PipelineOrchestrator.java — BA human review checkpoint humanReviewPhase.put(runId, REVIEW_PHASE_BA); controlSignals.put(runId, PipelineSignal.HUMAN_REVIEW_REQUESTED); String baReviewMsg = context.getJiraStoryKeys().isEmpty() ? \u0026#34;BA analysis complete. Review user stories and approve to proceed.\u0026#34; : \u0026#34;Jira stories created: \u0026#34; + String.join(\u0026#34;, \u0026#34;, context.getJiraStoryKeys()) + \u0026#34;. Review and approve to proceed.\u0026#34;; publishEvent(runId, AgentType.BA_AGENT, PipelineStatus.AWAITING_HUMAN_REVIEW, baReviewMsg, 0, REVIEW_PHASE_BA, context.getJiraStoryKeys()); The AppProperties.JiraProperties drives all connection parameters:\n// From: AppProperties.java public static class JiraProperties { private String baseUrl; // e.g. https://your-domain.atlassian.net private String projectKey; // e.g. SCRUM private String username; // email for Jira Cloud private String apiToken; private String storyIssueType = \u0026#34;Task\u0026#34;; private String testIssueType = \u0026#34;Task\u0026#34;; private String issueLinkType = \u0026#34;Relates\u0026#34;; private boolean enabled = true; } 11. Human-in-the-Loop Checkpoints #The pipeline has three pause points, each waiting for an explicit browser-side approval before proceeding:\nCheckpoint Phase Purpose 1 After BA Agent Review generated user stories in Jira before code generation starts 2 After all stories processed Approve final accumulated code before deployment to module (Legacy) 3 After test generation Review generated tests before executing them Implemented via a cooperative signal map:\n// From: PipelineOrchestrator.java public enum PipelineSignal { RUNNING, PAUSE_REQUESTED, CANCEL_REQUESTED, HUMAN_REVIEW_REQUESTED } private boolean checkpoint(UUID runId) { while (true) { PipelineSignal signal = controlSignals.getOrDefault(runId, PipelineSignal.RUNNING); switch (signal) { case CANCEL_REQUESTED -\u0026gt; { trackingService.updateRunStatus(runId, PipelineStatus.CANCELLED); controlSignals.remove(runId); return false; // caller returns, pipeline stops } case PAUSE_REQUESTED, HUMAN_REVIEW_REQUESTED -\u0026gt; { trackingService.updateRunStatus(runId, status); synchronized (controlSignals) { controlSignals.wait(30_000); } // Loop back — re-evaluate signal after wake } case RUNNING -\u0026gt; { return true; } } } } 12. What We Learned #1. Model selection matters more than prompt engineering. The best prompt in the world cannot make a 2B model reliably produce multi-file Spring Boot code. The jump from gemma4:e2b to qwen2.5-coder:7b removed entire categories of failure (missing imports, missing braces, missing FILE separators) that no prompt reinforcement could fix.\n2. Uniform num_ctx is not optional when running sequential agents. The 20–30 second model reload delay per stage — invisible during single-agent testing — compounds into minutes across a full pipeline. Always benchmark multi-agent pipelines with realistic stage-to-stage transitions, not individual calls.\n3. Deterministic output requires very low temperature. Code generation at temperature=0.1–0.3 produces consistent, repeatable output. Higher temperatures generated creative but broken Java — inconsistent method names, invented annotations that don\u0026rsquo;t exist, mismatched parameter types across files.\n4. Never pass full accumulated source to every story prompt. A signatures-only index is the practical solution to the context window problem. Four stories × 3 Java files × 200 lines each = 2,400 lines. Passed as signatures: ~60 lines. The LLM gets the structural contract without the noise.\n5. Structural pre-validation before LLM review saves tokens. Running a fast heuristic check for // === FILE:, balanced braces, and package declarations before invoking the Code Review LLM catches ~40% of failures in microseconds instead of the 30–60 seconds an LLM call costs.\n6. The database connection pool needs tuning for LLM workloads. Standard HikariCP defaults assume fast OLTP queries. LLM pipelines hold transactions open across calls that last minutes. The 30-second keepalive-time and tcpKeepAlive=true in the JDBC URL are not optional.\nRefer to the Github account for full code base.\nRepo Link: https://github.com/ankitagrahari/codeforgeai\nCodeForgeAI is built on Spring Boot 4.0.x, Spring AI, Vaadin Flow 25, Ollama, and PostgreSQL + PGVector. All LLM inference runs locally via Ollama on an Intel Core Ultra 7 265H with 32 GB RAM — no cloud API calls.\nIf you find this tutorial of some use, do comment, and share your thoughts, suggestions.\nHappy learning !!\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/codeforgeai-building-a-5-agent-multi-llm-pipeline-that-writes-reviews-tests-and-deploys-java-cod/","section":"Posts","summary":"\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e — CodeForgeAI is a Spring Boot + Vaadin application that orchestrates five specialised AI agents (Business Analyst → Code Generator → Code Reviewer → Test Generator → Test Executor) to transform a PDF requirements document into reviewed, tested, and deployed Java code — all running on-premise on a developer laptop, with no cloud LLM calls, no data leaving the machine.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"table-of-contents\" class=\"relative group\"\u003e\u003cstrong\u003eTable of Contents\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#table-of-contents\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003col\u003e\n\u003cli\u003eMotivation \u0026amp; Goals\u003c/li\u003e\n\u003cli\u003eTech Stack\u003c/li\u003e\n\u003cli\u003eEnd-to-End Pipeline Architecture\n\u003col\u003e\n\u003cli\u003eThe 5 Agents — Deep Dive\u003c/li\u003e\n\u003cli\u003eBusiness Analyst Agent\u003c/li\u003e\n\u003cli\u003eCode Generator Agent\u003c/li\u003e\n\u003cli\u003eCode Review Agent\u003c/li\u003e\n\u003cli\u003eTest Generator Agent\u003c/li\u003e\n\u003cli\u003eTest Executor Agent\u003c/li\u003e\n\u003c/ol\u003e\n\u003c/li\u003e\n\u003cli\u003eRAG: Codebase Ingestion \u0026amp; Vector Store\u003c/li\u003e\n\u003cli\u003eThe Hardest Part: Running Multiple LLMs Locally via Ollama\n\u003col\u003e\n\u003cli\u003eHardware Constraints\u003c/li\u003e\n\u003cli\u003eModels We Evaluated\u003c/li\u003e\n\u003cli\u003eThe num_ctx Uniformity Breakthrough\u003c/li\u003e\n\u003cli\u003eFinal Model Decision\u003c/li\u003e\n\u003cli\u003eOllama Environment Variables That Mattered\u003c/li\u003e\n\u003c/ol\u003e\n\u003c/li\u003e\n\u003cli\u003eChallenges We Solved\u003c/li\u003e\n\u003cli\u003eCode Structure\u003c/li\u003e\n\u003cli\u003eReal-Time UI with Vaadin \u0026amp; Server Push\u003c/li\u003e\n\u003cli\u003eJira Integration\u003c/li\u003e\n\u003cli\u003eHuman-in-the-Loop Checkpoints\u003c/li\u003e\n\u003cli\u003eWhat We Learned\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"1-motivation--goals\" class=\"relative group\"\u003e\u003cstrong\u003e1. Motivation \u0026amp; Goals\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#1-motivation--goals\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eGenerative AI is reshaping software development, but most AI coding tools are cloud-hosted, meaning proprietary code is sent to external servers. For enterprise environments where source code must never leave the network, that\u0026rsquo;s a hard blocker.\u003c/p\u003e","title":"CodeForgeAI: Building a 5-Agent Multi-LLM Pipeline That Writes, Reviews, Tests, and Deploys Java Code — Entirely Locally"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/llm/","section":"Categories","summary":"","title":"LLM"},{"content":"There was second discovery of a new vulnerability on the recent version of log4j 2.15.0.\nDenial of Service Attack #If the service or application is vulnerable to DoS attack, then a attacker can block the service or the application by bombarding it with infinite request, which will result in failure of the service because of the overburden resource utilization.\nHere is the definition from wikipedia\nIn computing, a denial-of-service attack (DoS attack) is a cyber-attack in which the perpetrator seeks to make a machine or network resource unavailable to its intended users by temporarily or indefinitely disrupting services of a host connected to a network. Denial of service is typically accomplished by flooding the targeted machine or resource with superfluous requests in an attempt to overload systems and prevent some or all legitimate requests from being fulfilled.[1]\nIn a distributed denial-of-service attack (DDoS attack), the incoming traffic flooding the victim originates from many different sources. This effectively makes it impossible to stop the attack simply by blocking a single source.[2]\n**CVE-2021-45046 ** #The Apache Log4j2 Thread Context Message Pattern and Context Lookup Pattern is vulnerable to a denial of service attack. It was found that the fix to address CVE-2021-44228 in Apache Log4j 2.15.0 was incomplete in certain non-default configurations.\nThis could allow attackers, in some situations, to craft malicious input data using a JNDI Lookup pattern resulting in a DoS attack. Log4j 2.15.0 restricts JNDI LDAP lookups to localhost by default. Note that previous mitigations involving configuration such as to set the system property log4j2.noFormatMsgLookup to true do NOT mitigate this specific vulnerability.\nMitigation # Java 8 (or later) users should upgrade to release 2.16.0. Users requiring Java 7 should upgrade to release 2.12.2 when it becomes available (work in progress, expected to be available soon). Otherwise, remove the JndiLookup class from the classpath: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class Note that only the log4j-core JAR file is impacted by this vulnerability. Applications using only the log4j-api JAR file without the log4j-core JAR file are not impacted by this vulnerability.\nSo a better approach now to tackle this vulnerability is to upgrade the log4j version to 2.16.0\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/log4j-vulnerability-continued/","section":"Posts","summary":"\u003cp\u003eThere was second discovery of a new vulnerability on the recent version of log4j 2.15.0.\u003c/p\u003e\n\u003ch2 id=\"denial-of-service-attack\" class=\"relative group\"\u003eDenial of Service Attack \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#denial-of-service-attack\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eIf the service or application is vulnerable to DoS attack, then a attacker can block the service or the application by bombarding it with infinite request, which will result in failure of the service because of the overburden resource utilization.\u003c/p\u003e","title":"Log4j Vulnerability - Continued"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/multiagent/","section":"Categories","summary":"","title":"MultiAgent"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/multiagent/","section":"Tags","summary":"","title":"MultiAgent"},{"content":"In this post we will look into Scraping any Youtube video metadata to get the views, likes, dislikes, published date etc. information using Python. We will not be using any third party libraries which parse the HTML like Beautiful Soup.\nProblem Statement: #To fetch Youtube video metadata like its View count, Likes/Dislikes count, Published Date, Title of the video etc. without using any 3rd party libraries which parses HTML or Javascript. To add to that, we will not be using the Youtube API to fetch data.\nApproach #There are multiple ways to fetch the data from youtube, or any other website. In order to parse the website, Beautiful Soup library can be used. But we will use even simpler way to get out required data-set.\nWe will be using python requests library to get the page source of the youtube video link and ***json ***library to parse the text to Json object.\nThere is a pattern to any Youtube video page source. When you open the page source, search for \u0026lsquo;ytInitialData\u0026rsquo; in the page source, and you will find that in the script tag, this variable is assigned with a Json. Only problem is that, we cannot use it as it is, and needs some manipulation. So basically, we will be manipulating strings in Python to get the data we want.\nTo get the exact Json, I need to find the start and end Index of this Json from the page source. Lets try it out for any Youtube video you are watching.\nFor getting the page source of any website, you can right click on the website and click on the *View Page Source *option\nSearch for ytInitialData in the tab where the page source is open. Actual string will be like this: Code #Lets code it in Python step by step:\nYou need to import ***json ***and ***requests ***libraries as the prerequisite step.\nimport json import requests From the given video link, get the page source using request library and save the text format to some variable\ndef import_video_data(URL): print(\u0026#39;Fetching Video page source using URL \u0026#39; + URL) # window[\u0026#34;ytInitialData\u0026#34;] = page_source = requests.get(URL) page_source = page_source.text Extract the json data from the page source of the given video link.\nstart_index = page_source.find(\u0026#39;ytInitialData\u0026#39;) tmp = page_source[ start_index+17:] end_index = tmp.find(\u0026#39;}};\u0026#39;) tmp = tmp[:end_index] + \u0026#39;}}\u0026#39; return tmp Now, parse the string returned as ***Json ***object using Json library. Here we are using loads() method to convert the Json string into a dictionary.\ndef parse_json(json_data): json_dict = json.loads(json_data) return json_dict The Json we fetched in the last step will look something like this: From the Json, we can now extract any information we want.\nThe basic manual way to get the title, video Id, View Count, Likes, Dislikes, their short form and its published date are displayed here:\nFor Video Id:\nvideo_id = yt_json[\u0026#39;currentVideoEndpoint\u0026#39;][\u0026#39;watchEndpoint\u0026#39;][\u0026#39;videoId\u0026#39;] print(\u0026#39;VideoId:\u0026#39; + video_id) For Title:\ntitle = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;title\u0026#39;][\u0026#39;runs\u0026#39;][0][\u0026#39;text\u0026#39;] print(\u0026#39;Title:\u0026#39;+ title) For View count and its short form:\nviews = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;viewCount\u0026#39;][\u0026#39;videoViewCountRenderer\u0026#39;][\u0026#39;viewCount\u0026#39;][\u0026#39;simpleText\u0026#39;] short_views = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;viewCount\u0026#39;][\u0026#39;videoViewCountRenderer\u0026#39;][\u0026#39;shortViewCount\u0026#39;][\u0026#39;simpleText\u0026#39;] print(\u0026#39;Views:\u0026#39; + views + \u0026#39; in short:\u0026#39;+ short_views) For Likes and its short form:\nlikes = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;videoActions\u0026#39;][\u0026#39;menuRenderer\u0026#39;][\u0026#39;topLevelButtons\u0026#39;][0][\u0026#39;toggleButtonRenderer\u0026#39;][\u0026#39;defaultText\u0026#39;][\u0026#39;accessibility\u0026#39;][\u0026#39;accessibilityData\u0026#39;][\u0026#39;label\u0026#39;] likes_inshort = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;videoActions\u0026#39;][\u0026#39;menuRenderer\u0026#39;][\u0026#39;topLevelButtons\u0026#39;][0][\u0026#39;toggleButtonRenderer\u0026#39;][\u0026#39;defaultText\u0026#39;][\u0026#39;simpleText\u0026#39;] print(\u0026#39;Likes:\u0026#39;+ likes+\u0026#39; in short:\u0026#39;+ likes_inshort) For Dislikes and its short form:\ndislikes = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;videoActions\u0026#39;][\u0026#39;menuRenderer\u0026#39;][\u0026#39;topLevelButtons\u0026#39;][1][\u0026#39;toggleButtonRenderer\u0026#39;][\u0026#39;defaultText\u0026#39;][\u0026#39;accessibility\u0026#39;][\u0026#39;accessibilityData\u0026#39;][\u0026#39;label\u0026#39;] dislikes_inshort = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;videoActions\u0026#39;][\u0026#39;menuRenderer\u0026#39;][\u0026#39;topLevelButtons\u0026#39;][1][\u0026#39;toggleButtonRenderer\u0026#39;][\u0026#39;defaultText\u0026#39;][\u0026#39;simpleText\u0026#39;] print(\u0026#39;Dis-Likes:\u0026#39;+ dislikes+\u0026#39; in short:\u0026#39;+ dislikes_inshort) For Published Date:\npublished_date = yt_json[\u0026#39;contents\u0026#39;][\u0026#39;twoColumnWatchNextResults\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;results\u0026#39;][\u0026#39;contents\u0026#39;][0][\u0026#39;videoPrimaryInfoRenderer\u0026#39;][\u0026#39;dateText\u0026#39;][\u0026#39;simpleText\u0026#39;] print(\u0026#39;Published Date:\u0026#39;+ published_date) The output of the above code will be look like this:\nFetching Video stats (as per date 3rd August, 2020) #**URL: **https://www.youtube.com/watch?v=JRtgXN-bwGE **VideoId: **JRtgXN-bwGE **Title: **Honest Review | Raat Akeli Hai, Shakuntala Devi \u0026amp; Lootcase | MensXP **Views: **282,420 views in short:282K views **Likes: **21,343 likes in short: 21K **Dis-Likes: **413 dislikes in short:413 **Published Date: **1 Aug 2020\nUsing the above technique, you will have the Json and can extract any data which is required. You can find this entire code on my Github Repository.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/scraping-youtube-metadata-in-python-using-requests-and-json-libraries-only/","section":"Posts","summary":"\u003cp\u003eIn this post we will look into \u003cem\u003e\u003cstrong\u003eScraping any Youtube video\u003c/strong\u003e\u003c/em\u003e metadata to get the views, likes, dislikes, published date etc. information using \u003cem\u003e\u003cstrong\u003ePython\u003c/strong\u003e\u003c/em\u003e. We will not be using any third party libraries which parse the HTML like Beautiful Soup.\u003c/p\u003e\n\u003ch3 id=\"problem-statement\" class=\"relative group\"\u003e\u003cstrong\u003eProblem Statement:\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem-statement\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eTo fetch Youtube video metadata like its View count, Likes/Dislikes count, Published Date, Title of the video etc. without using any 3rd party libraries which parses HTML or Javascript. To add to that, we will not be using the \u003cem\u003e\u003cstrong\u003eYoutube API\u003c/strong\u003e\u003c/em\u003e to fetch data.\u003c/p\u003e","title":"Scraping Youtube Videos - Using Python's Requests \u0026 JSON Libraries"},{"content":"In this post we will create a simple Spring JPA project which will connect to MongoDB and create a document, where we will save records and then will try to fetch those records using JPA repository built in methods.\nIn this post we will try performing all CRUD Operation on MongoDB Using JSON queries in Spring Data JPA\nProject Structure #We will create Employee repository which will store the Employee models, and will create Test cases to fetch data based on JSON queries from MongoDB on my local.\nDependencies #We will be using *lombok *and spring starter mongodb to start with.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-mongodb\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-test\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; Configuration #The application properties will have the spring data attributes which will tell the spring project about the hostname, port, username, password, database etc. We can define like this\nserver.port=8768 spring.data.mongodb.uri= mongodb://127.0.0.1:27017/employees spring.data.mongodb.database=employees spring.data.mongodb.repositories.enabled=true #spring.data.mongodb.authentication-database= # Authentication database name. #spring.data.mongodb.database=test # Database name. #spring.data.mongodb.field-naming-strategy= # Fully qualified name of the FieldNamingStrategy to use. #spring.data.mongodb.grid-fs-database= # GridFS database name. #spring.data.mongodb.host=localhost # Mongo server host. #spring.data.mongodb.password= # Login password of the mongo server. #spring.data.mongodb.port=27017 # Mongo server port. #spring.data.mongodb.repositories.enabled=true # Enable Mongo repositories. #spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. When set, host and port are ignored. #spring.data.mongodb.username= # Login user of the mongo server. You can refer to other commented spring data monogDB parameters which can be changed during the development cycle.\nModel #This is the *Employee *model which will have name, address, and experience attributes.\n@Data @AllArgsConstructor @NoArgsConstructor @Document(collection = \u0026#34;employee\u0026#34;) public class Employee { // @Id // private String id; private String name; private String address; private Integer experience; } Here, we can add the document collection name (~table) using the @Document annotation. By default, if the @Document is not given, it will map it to the class name. It will also create the employee document in MongoDB if it is not present.\nAfter the data insertion in DB, it will look like this Notice that the id column is commented, but while storing to the MongoDB, it will add the _id field. Also, since we are using lombok, we don\u0026rsquo;t have to write the boiler-plate code because lombok, will automatically add the getters and setter methods. Also we have asked to add all argument and no argument constructors.\nRepository #The *Employee *repository will extends *MongoRepository *class where we can define the key and value\u0026rsquo;s data type. It also will have all the built-in methods and include any custom queries which we want to fetch data with.\n**Fetch Data **byName #The employee repository will fetch data of employees by name attributes using this field\n@Query(\u0026#34;{name:\u0026#39;?0\u0026#39;}\u0026#34;) List\u0026lt;Employee\u0026gt; findEmployeeByName(String name); We can also skip the @Query annotation.\nFetch Data byName using Regex #We can fetch data of employees by giving a substring of the name. Like using regex we can get data whose name contains the specific string.\n@Query(\u0026#34;{ \u0026#39;name\u0026#39; : { $regex: ?0 } }\u0026#34;) List\u0026lt;Employee\u0026gt; findEmployeeByRegexpName(String regexp); **Fetch Data **byExperience #We can fetch employees whose experience is equals to a certain number, like this\n@Query(value = \u0026#34;{experience:\u0026#39;?0\u0026#39;}\u0026#34;, fields = \u0026#34;{\u0026#39;name\u0026#39;:1, \u0026#39;address\u0026#39;:1}\u0026#34;) List\u0026lt;Employee\u0026gt; findEmployeeByExperienceEquals(int exp); **Fetch Data *****byExperience ***using custom query #We can fetch employees whose experience is between a set of numbers, like this\n@Query(\u0026#34;{\u0026#39;experience\u0026#39;: { $gt: ?0, $lt: ?1 } }\u0026#34;) List\u0026lt;Employee\u0026gt; findEmployeesByExperienceBetween(int start, int end); Unit Testing #As part of unit testing our spring boot project, we can use the @SpringBootTest annotation and add the Junit @Test or @BeforeAll or @BeforeEach, @After, @AfterEach annotations to create a test class, or to run certain methods before each or after each test case run.\nWe will setup by creating few Employee records and insert into the MongoDB.\n@BeforeEach void setUp(){ //Remove everything from Employee table employeeRepo.deleteAll(); //Insert new Employee data Employee employee = new Employee(\u0026#34;dynamically\u0026#34;, \u0026#34;Amsterdam\u0026#34;, 9); Employee employee1 = new Employee(\u0026#34;blunt\u0026#34;, \u0026#34;India\u0026#34;, 5); Employee employee2 = new Employee(\u0026#34;tech\u0026#34;, \u0026#34;Germany\u0026#34;, 6); Employee employee3 = new Employee(\u0026#34;Subscribe\u0026#34;, \u0026#34;Austin\u0026#34;, 9); empList.add(employee); empList.add(employee1); empList.add(employee2); empList.add(employee3); employeeRepo.saveAll(empList); System.out.println(\u0026#34;Employees data saved\u0026#34;); } This method will run before every test case run, so that the new Junit will work on separate and new dataset.\nWe will add other test cases to run different test cases.\n@Test void fetchDataByName(){ List\u0026lt;Employee\u0026gt; employees = employeeRepo.findEmployeeByName(\u0026#34;tech\u0026#34;); Assertions.assertEquals(\u0026#34;tech\u0026#34;, employees.get(0).getName()); } @Test void fetchDataByNamePart(){ List\u0026lt;Employee\u0026gt; employees = employeeRepo.findEmployeeByRegexpName(\u0026#34;c\u0026#34;); Assertions.assertEquals( empList.stream().filter(emp -\u0026gt; emp.getName().contains(\u0026#34;c\u0026#34;)).count(), employees.size() ); } The above test case will have fetch data by Name as defined above.\nSimilarly, we can define other test cases which will call the methods to fetch data based on experience attributes.\n@Test void fetchDataByExp(){ List\u0026lt;Employee\u0026gt; employees = employeeRepo.findEmployeeByExperienceEquals(9); Assertions.assertEquals( empList.stream().filter(emp -\u0026gt; emp.getExperience().equals(9)).count(), employees.size() ); } @Test void fetchDataByExpBetweenFiveAndTen(){ List\u0026lt;Employee\u0026gt; employees = employeeRepo.findEmployeesByExperienceBetween(6, 10); Assertions.assertEquals( empList.stream().filter(emp -\u0026gt; emp.getExperience()\u0026gt;6 \u0026amp;\u0026amp; emp.getExperience()\u0026lt;10).count(), employees.size() ); } You might face an exception if the @Query annotation is not properly constructued.\norg.bson.BsonInvalidOperationException: readStartDocument can only be called when CurrentBSONType is DOCUMENT, not when CurrentBSONType is STRING.\nThis will conclude the creation, fetching and deletion of data using Spring Data JPA and MongoDB. You can find the entire codebase in my Github repository.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/spring-data-jpa-with-mongodb/","section":"Posts","summary":"\u003cp\u003eIn this post we will create a simple Spring JPA project which will connect to MongoDB and create a document, where we will save records and then will try to fetch those records using JPA repository built in methods.\u003c/p\u003e\n\u003cp\u003eIn this post we will try performing all CRUD Operation on MongoDB Using JSON queries in Spring Data JPA\u003c/p\u003e\n\u003ch2 id=\"project-structure\" class=\"relative group\"\u003e\u003cstrong\u003eProject Structure\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#project-structure\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eWe will create Employee repository which will store the Employee models, and will create Test cases to fetch data based on JSON queries from MongoDB on my local.\u003c/p\u003e","title":"Spring Data JPA with MongoDB"},{"content":"As we discussed in the previous post on Domain 3: Claude Code Configuration and Workflow, this part of the post will cover the Domain 4 which holds 20% weightage in the exam. #If you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. #Where to Register: https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\nUnofficial practice set, written to match publicly reported blueprint topics — not sourced from the real exam bank. Use for concept drilling, not as a guarantee of exam content.\nTopics Covered #Explicit Criteria in Prompts (reducing false positives)\nFew-Shot Prompting\nStructured Output Enforcement (tool_use + JSON Schema)\ntool_choice Modes (recap, applied to structured output)\nValidation \u0026amp; Retry Loops\nBatch Processing (Message Batches API)\nMulti-Instance Review Architectures\nMulti-Pass Review Architectures\n1. Explicit Criteria in Prompts #Replacing vague instructions (\u0026ldquo;be conservative,\u0026rdquo; \u0026ldquo;use good judgment\u0026rdquo;) with concrete, checkable thresholds and definitions — exact numbers, explicit categories, clear boundaries. Vague criteria cause inconsistent behavior because \u0026ldquo;conservative\u0026rdquo; means something different call to call.\nQ1. A fraud-review prompt says \u0026ldquo;flag transactions that seem risky.\u0026rdquo; Reviewers report wildly inconsistent flagging. What\u0026rsquo;s the root cause?\nA) The model is too weak for this task\nB) \u0026ldquo;Seems risky\u0026rdquo; is a vague criterion with no concrete threshold — different runs interpret it differently\nC) The prompt is too long\nD) This requires a multi-agent system, not a prompt fix\nAnswer: B — fix is to define explicit thresholds, e.g., \u0026ldquo;flag if amount \u0026gt; $10,000 AND shipping country ≠ billing country.\u0026rdquo;\nQ2. Which instruction is better designed to reduce false positives?\nA) \u0026ldquo;Only escalate if you\u0026rsquo;re really sure it\u0026rsquo;s a problem\u0026rdquo;\nB) \u0026ldquo;Escalate only if at least 2 of these 3 conditions are met: [explicit list]. If fewer than 2 are met, do not escalate even if something feels off.\u0026rdquo;\nC) \u0026ldquo;Use your best judgment on escalation\u0026rdquo;\nD) \u0026ldquo;Escalate conservatively\u0026rdquo;\nAnswer: B — explicit, checkable conditions instead of subjective language.\nQ3. True/False: Making criteria explicit primarily helps semantic correctness (getting the right answer), not just format consistency.\nAnswer: True — explicit criteria reduce inconsistent judgment calls, which is a correctness/precision problem, separate from output formatting.\n2. Few-Shot Prompting #Providing 2–4 targeted examples (input → correct output, ideally with brief reasoning) rather than relying purely on descriptive instructions. Most effective when examples target the cases the model actually gets wrong — ambiguous or overlapping categories — not just easy \u0026ldquo;happy path\u0026rdquo; cases.\nQ4. A classification prompt has a long paragraph describing each category but still misclassifies edge cases. What\u0026rsquo;s the more effective fix?\nA) Add 2–4 few-shot examples focused specifically on the ambiguous/boundary cases, including the reasoning for the correct label\nB) Make the paragraph even longer\nC) Remove all instructions and rely on zero-shot\nD) Increase max_tokens\nAnswer: A — few-shot examples targeted at hard cases beat longer descriptive instructions for consistency.\nQ5. Why include the rationale in a few-shot example, not just the input/output pair?\nA) It\u0026rsquo;s optional decoration\nB) The rationale teaches the model why that answer is correct, which generalizes better to new ambiguous cases than the answer alone\nC) Rationale is required by the API\nD) It has no effect on output quality\nAnswer: B.\nQ6. A team writes 4 few-shot examples, all clear-cut \u0026ldquo;easy\u0026rdquo; cases with obvious answers. Classification accuracy barely improves. Why?\nA) Few-shot prompting doesn\u0026rsquo;t work\nB) 4 examples is too many\nC) The examples should have been longer\nD) Examples should target where the model actually struggles (ambiguous/overlapping cases); easy-case examples add little signal since the model already handles those correctly\nAnswer: D.\n3. Structured Output Enforcement (tool_use + JSON Schema) #Using a tool call with a defined JSON Schema (rather than asking Claude to \u0026ldquo;please output JSON\u0026rdquo; in prose) to force output into a fixed, parseable structure.\nThis guarantees syntactic correctness — no missing braces, no malformed JSON But does NOT guarantee semantic correctness — the structure can be perfectly valid and still contain a wrong answer. Q7. A team asks Claude to \u0026ldquo;respond only in JSON\u0026rdquo; via plain prompt instructions, but production logs show occasional malformed JSON breaking the parser. What\u0026rsquo;s the fix?\nA) Add more emphatic language like \u0026ldquo;IMPORTANT: only JSON!!\u0026rdquo;\nB) Switch to XML instead\nC) There is no fix; malformed output is unavoidable\nD) Enforce structure via tool_use with a defined JSON Schema instead of relying on prompt-based formatting instructions\nAnswer: D — programmatic enforcement (schema) vs. prompt-based guidance is a core distinction this domain tests directly.\nQ8. A resume-parsing tool uses a JSON Schema and never produces malformed JSON. However, it sometimes extracts the wrong candidate\u0026rsquo;s phone number into the phone field. What does this demonstrate?\nA) The schema is broken\nB) JSON Schema enforcement guarantees syntactic validity but not semantic correctness — the output can be well-formed and still wrong\nC) tool_use cannot be used for extraction tasks\nD) This means few-shot prompting failed\nAnswer: B — a specifically high-yield exam point per reported test-taker notes.\nQ9. When designing a schema field for a category that might not cleanly fit any known option, which pattern is best?\nA) Include an \u0026ldquo;other\u0026rdquo; enum value paired with a free-text detail_string field to capture what didn\u0026rsquo;t fit\nB) Force it into the closest existing enum value regardless of fit\nC) Leave the field out of the schema entirely\nD) Make every field nullable with no enum at all\nAnswer: A — schema design tip: \u0026quot;other\u0026quot; + detail_string avoids forcing bad fits into rigid categories, and also avoids losing information entirely.\nQ10. Why might a schema field be marked nullable rather than always-required?\nA) Nullable fields are never allowed in JSON Schema\nB) Some information genuinely may not be present in the source input; forcing a required field invites the model to fabricate a value rather than honestly report \u0026ldquo;not found\u0026rdquo;\nC) It reduces token cost\nD) It has no practical purpose\nAnswer: B.\nQ11. An extraction schema uses an \u0026ldquo;unclear\u0026rdquo; enum option for a confidence-sensitive field. What problem is this designed to prevent?\nA) The model guessing/fabricating a confident-sounding answer when the source data is genuinely ambiguous, instead of being able to honestly flag uncertainty\nB) Slower response times\nC) JSON parsing errors\nD) Tool selection errors\nAnswer: A.\n4. tool_choice Modes Applied to Structured Output #(Builds on Domain 2) In structured-output contexts specifically: use a forced/specific tool_choice when the output must always conform to one schema; use auto when Claude should decide whether structured output or free text is appropriate for that turn; any when some tool call is required but which one is flexible.\nQ12. A data-extraction pipeline must always return results in exactly one schema, with zero chance of a free-text response. Which tool_choice setting is correct?\nA) auto\nB) A forced/specific named tool\nC) none\nD) any\nAnswer: B.\nQ13. A general-purpose assistant sometimes needs to extract structured data and sometimes just needs to chat normally. Which tool_choice setting fits this mixed use case?\nA) A forced specific tool always\nB) auto — letting Claude decide per-turn whether structured output is needed\nC) none at all times\nD) any\nAnswer: B.\n5. Validation \u0026amp; Retry Loops #When extraction/output fails validation, send back the original input + the specific validation error + the failed attempt, and ask the model to correct it — rather than just re-asking from scratch.\nImportant limitation: retry loops fix cases where the correct data exists but was mis-extracted; they do NOT help when the information simply isn\u0026rsquo;t present in the source — no amount of retrying recovers data that was never there.\nQ14. A validation-retry loop is built for a document-extraction system, but for a subset of documents the field being extracted was never present in the source text at all. Retries don\u0026rsquo;t help. What\u0026rsquo;s the correct diagnosis?\nA) The retry loop is poorly implemented and needs more attempts\nB) Retry loops fix mis-extraction of present data, not absence of data; more retries won\u0026rsquo;t manufacture information that isn\u0026rsquo;t in the source\nC) This means JSON Schema enforcement is broken\nD) The model needs a larger context window\nAnswer: B — a specifically called-out exam trap: know what retry loops can and can\u0026rsquo;t fix before building one.\nQ15. What should a well-designed retry prompt include after a validation failure?\nA) Just the original prompt again, unchanged\nB) A completely different prompt with no reference to the failure\nC) The original input, the specific validation error, and the failed attempt — giving the model concrete context on what went wrong\nD) Nothing — just increase temperature and retry\nAnswer: C.\nQ16. A team keeps adding more retry attempts (now at 10 retries) to fix a persistently low extraction success rate, with no improvement. What should they check first?\nA) Whether the source documents actually contain the data being requested\nB) Whether temperature is set high enough\nC) Whether the schema has too many fields\nD) Whether tool_choice is set to auto\nAnswer: A — directly tests the retry-loop limitation above.\n6. Batch Processing (Message Batches API) #An API mode for processing large volumes of independent requests asynchronously — roughly 50% cost savings, but with up to a 24-hour processing window and no latency SLA.\nRight for large, non-urgent, non-blocking workloads (overnight reports, bulk classification). Wrong for anything a user or pipeline is actively waiting on. Q17. A team wants to classify 500,000 historical support tickets overnight for an analytics dashboard, with no user waiting on the result. What\u0026rsquo;s the appropriate approach?\nA) Real-time synchronous API calls for all 500,000, one at a time\nB) The Message Batches API — cost savings matter more than latency here, and there\u0026rsquo;s no blocking wait\nC) A blocking CI/CD pipeline step\nD) Plan Mode\nAnswer: B.\nQ18. A pre-merge CI check needs Claude\u0026rsquo;s review before a developer can merge their PR (they\u0026rsquo;re actively waiting). Should this use the Batch API?\nA) Yes, always use Batch API for cost savings\nB) No — batch processing has no latency SLA (up to 24 hours); blocking, developer-waiting workflows need synchronous calls, not batch\nC) Yes, because CI/CD always requires batch mode\nD) It doesn\u0026rsquo;t matter which is used\nAnswer: B — explicitly called out: keep blocking workflows synchronous.\nQ19. What is the main trade-off of using the Message Batches API?\nA) Higher cost, faster response\nB) No cost difference, just simpler code\nC) ~50% lower cost, but no latency guarantee — some requests may take up to 24 hours\nD) It only supports single requests, not batches\nAnswer: C.\n7. Multi-Instance Review Architectures #Running multiple independent instances/calls of Claude on the same input (e.g., in parallel, with no shared context) and comparing/aggregating their outputs — used to catch inconsistency or improve confidence via consensus, since independent instances won\u0026rsquo;t share the same blind spot.\nQ20. A high-stakes classification task wants to reduce the chance of a single flawed judgment slipping through. What\u0026rsquo;s a multi-instance approach to this?\nA) Run the same prompt once, trust the result\nB) Increase the prompt length once\nC) Use the Batch API instead\nD) Run the same input through multiple independent Claude calls (no shared context) and compare results — e.g., flag for human review if they disagree\nAnswer: D.\nQ21. Why does an independent instance without prior generation context catch more errors during review than the same instance reviewing its own earlier output?\nA) Independent instances are always smarter\nB) The generating instance tends to rationalize/defend its own prior decisions; a fresh instance with no investment in the earlier output evaluates it more objectively\nC) There\u0026rsquo;s no actual difference\nD) Independent instances have access to more tools\nAnswer: B — directly ties back to Domain 1\u0026rsquo;s \u0026ldquo;same-session self-review\u0026rdquo; anti-pattern, tested again here from the structured-output angle.\n8. Multi-Pass Review Architectures #Running review as sequential passes, each with a distinct, narrower purpose (e.g., pass 1: extract, pass 2: validate against source, pass 3: format/finalize) — rather than trying to do extraction, validation, and formatting all in one shot. Different from multi-instance (parallel, independent) — multi-pass is sequential and each pass builds on/checks the previous one.\nQ22. An extraction pipeline currently tries to extract, self-validate, and format output all in a single prompt/call, with mixed reliability. What\u0026rsquo;s a multi-pass redesign?\nA) Combine everything into an even longer single prompt\nB) Split into sequential passes — e.g., extract first, then a separate pass validates the extraction against the source, then a final pass formats — each with a narrow, focused job\nC) Run the same single-call prompt multiple times in parallel\nD) Switch to Batch API\nAnswer: B.\nQ23. What\u0026rsquo;s the key structural difference between multi-instance and multi-pass review architectures?\nA) Multi-instance runs independent parallel copies on the same input for comparison; multi-pass runs sequential, dependent stages where each stage has a distinct narrower job\nB) They are the same thing\nC) Multi-pass is always cheaper\nD) Multi-instance requires the Batch API\nAnswer: A.\nRealistic Scenario Based Questions (cross-topic) #Q24. A legal-document extraction system uses tool_use with a strict JSON Schema. QA reports zero malformed JSON in six months, but a recent audit found the system had been extracting the wrong party\u0026rsquo;s name into the counterparty field for certain contract formats. What\u0026rsquo;s the correct diagnosis and next architectural step?\nA) The schema is broken; add more required fields\nB) This is a semantic correctness issue, not a syntactic one — JSON Schema guarantees valid structure, not accuracy. Add few-shot examples targeting that ambiguous contract format, and/or a validation pass checking extracted values against the source\nC) Switch to prompt-based JSON instructions instead of tool_use\nD) This means the Batch API was misconfigured\nAnswer: B — combines Q7–8\u0026rsquo;s core lesson with the fix from few-shot (Q4) and multi-pass validation (Q22).\nQ25. A support-ticket triage system needs:\n(1) consistent escalation decisions instead of ad hoc judgment calls, and\n(2) recovery when a first extraction attempt mis-parses a field that IS present in the ticket text.\nWhich two techniques directly address these two needs respectively?\nA) Batch API for both\nB) Multi-instance review for both\nC) Few-shot prompting for #1 only, nothing for #2\nD) Explicit criteria (concrete escalation thresholds) for #1, and a validation-retry loop for #2\nAnswer: D.\nQ26. A pipeline classifies 2 million product listings nightly (no one waiting) and separately powers a live chat feature answering customer questions in real time. Which processing approach fits each, and why?\nA) Batch API for both, since batch always saves money\nB) Synchronous calls for both\nC) Batch API for the nightly 2M-listing job (non-blocking, cost matters more than latency); synchronous real-time calls for the live chat (user is actively waiting, no latency SLA is acceptable)\nD) Batch API for chat, synchronous for the nightly job\nAnswer: C\nQ27. A code-review agent generates a fix, then reviews its own fix in the same session and approves it. A separate audit shows a different, fresh Claude instance catches issues the original missed. What principle explains this, and what architecture should the team adopt?\nA) The first instance is defective; replace the model\nB) Same-session self-review tends to rationalize prior output; adopt a multi-instance (independent reviewer) architecture instead of self-review\nC) This means multi-pass architecture is broken\nD) No architectural change is needed\nAnswer: B.\nQuick Reference Table # Concept One-line definition Explicit criteria Concrete, checkable thresholds instead of vague instructions Few-shot prompting 2–4 targeted examples (with reasoning) aimed at hard/ambiguous cases tool_use + JSON Schema Enforces syntactic structure; does NOT guarantee semantic correctness \u0026ldquo;other\u0026rdquo; + detail_string Schema pattern for values that don\u0026rsquo;t fit existing enums Nullable field Lets the model honestly report \u0026ldquo;not found\u0026rdquo; instead of fabricating \u0026ldquo;unclear\u0026rdquo; enum Lets the model flag genuine ambiguity instead of guessing confidently Validation-retry loop Send input + error + failed attempt back; fixes mis-extraction, not absent data Message Batches API ~50% cheaper, up to 24hr window, no latency SLA — for non-blocking bulk work Multi-instance review Independent parallel calls compared/aggregated for consensus Multi-pass review Sequential stages, each with a narrow, distinct job *High-yield traps reported by past test-takers: *\n*(1) JSON Schema = syntax only, not correctness (Q7-8, Q24); *\n*(2) retry loops can\u0026rsquo;t recover data that isn\u0026rsquo;t in the source (Q14, Q16); *\n*(3) Batch API vs synchronous is a latency/blocking decision, not just a cost decision (Q17-19, Q26); *\n(4) same-session self-review vs independent review reappears here from Domain 1, now framed around structured-output QA (Q21, Q27).\nReferring to these questions just gives you an idea how the exam looks like, and can vary. Share if you have any answers or questions not correctly placed or explained.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/cca-f-domain-4-prompt-engineering-and-structured-output/","section":"Posts","summary":"\u003ch3 id=\"as-we-discussed-in-the-previous-post-on-domain-3-claude-code-configuration-and-workflow-this-part-of-the-post-will-cover-the-domain-4-which-holds-20-weightage-in-the-exam\" class=\"relative group\"\u003eAs we discussed in the previous post on \u003ca href=\"/post/cca-f-domain-3-claude-code-configuration-and-workflows-key-concepts\"\u003eDomain 3: Claude Code Configuration and Workflow\u003c/a\u003e, this part of the post will cover the Domain 4 which holds 20% weightage in the exam. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#as-we-discussed-in-the-previous-post-on-domain-3-claude-code-configuration-and-workflow-this-part-of-the-post-will-cover-the-domain-4-which-holds-20-weightage-in-the-exam\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003ch3 id=\"if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" class=\"relative group\"\u003eIf you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eWhere to Register: \u003ca href=\"https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\u003c/a\u003e\u003c/p\u003e","title":"CCA-F Domain 4: Prompt Engineering and Structured Output"},{"content":"In the previous post about utilizing Spring AI with Ollama and employing Docker Model Runner to execute AI models locally, this post will expand on that by developing a chatbot. This chatbot will retain interaction data in memory, facilitating more meaningful communication.\nIn this post, we will use the Docker Model Runner to run Google\u0026rsquo;s Gemma AI model in our local machine with OpenAI, and create a spring boot AI chat application which will generate responses based on the previous context.\nLet\u0026rsquo;s first setup our Docker, and run the Google AI model gemma in our local.\nRunning AI model Gemma locally #So, first step will be to open terminal and run command\ndocker model list which will list down existing AI model present in your local. We have already downloaded and ran ai/gemma3 in the last post. Here, we will only look into the connection parameters that will be required in the spring boot project.\nWhen we enabled the docker model runner, there is a enable host-side TCP support check-box, which enabled the TCP port, using which we will connect to the already running AI model.\nSo, we will be using this port, and the base-url for the open-ai parameter will be http://localhost:12434/engines\nSetup Spring AI Project #Lets navigate to start.spring.io to initialise a spring boot project which will have OpenAI and Spring Web as the dependencies.\nWe will have the following application.properties file.\nspring.application.name=chatbot spring.ai.openai.base-url=http://localhost:12434/engines spring.ai.openai.chat.options.model=ai/gemma3 spring.ai.openai.api-key=test # Docker Model Runner doesn\u0026#39;t support embeddings, so we need to disable them. spring.ai.openai.embedding.enabled=false NOTE: The api-key is given as test, and not a specific API key. This is only possible when you have the AI model running locally, else it is mandatory to create an API key from OpenAI.\nCreating Chat Client Configuration #We need to create ChatClient object to communicate to any AI model in Spring AI. In the previous post we have created a ChatClient object which took a default system prompt, that was configurable. This time, when we are trying to store the chat history in memory, we will be using Advisors.\nAdvisor is a powerful tool which helps in intercepting and modifying the AI request and response. It also helps in adding more context to the interaction, by adding functionalities like logging, memory management, data transformation without changing applications core logic.\nTypes of Spring AI Advisors:\nCall Around Advisors Chat Memory Advisors, Retrievers Advisors Logging Advisors So, in order to create a Chat memory advisor for our project, we will use the ChatClient builder to set the default advisor for our application.\n@Autowired ChatMemory chatMemory; @Bean ChatClient client(ChatClient.Builder builder){ return builder.defaultAdvisors( MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } With the new upgrade from Spring, InMemoryChatMemory was deprecated, and now Spring auto-configures ChatMemory bean when any AI model starters is used. By default it comes with MessageWindowChatMemory implementation which stores the conversation history in memory based on the default window maximum size. To manage the conversational history, the storage mechanism is decoupled from ChatMemory interface, and is now handled by ChatMemoryRepository interface.\nAfter creating the ChatClient bean, we can use it by auto-wiring it or by constructor injection in our Controller, where we will create a Get API, that will send the AI model with the message coming as request param.\nLets try to test this application, where we will ask the AI model with two separate GET request, which is supposed to be stateless, but in our use case the AI model, will be having a history of the conversation, therefore will be able to answer the next question. AI communication which remembers my name!\nThis can be extended to a nicer UI which will be much easier to work with, instead of sending multiple API request. Also, this is a very simple usage of how we can create a chat bot, which can have the context of the conversation history.\nReferences # https://docs.spring.io/spring-ai/reference/1.0/upgrade-notes.html https://docs.spring.io/spring-ai/reference/api/advisors.html https://docs.spring.io/spring-ai/reference/api/chat/dmr-chat.html The project is added to my Github repo, if you want to explore more. Hoping this will be helpful in learning Spring AI and using different AI models. Please comment down your thoughts or suggestions to improve.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/chatbot-using-spring-ai-and-openai/","section":"Posts","summary":"\u003cp\u003eIn the previous post about utilizing Spring AI with Ollama and employing Docker Model Runner to execute AI models locally, this post will expand on that by developing a chatbot. This chatbot will retain interaction data in memory, facilitating more meaningful communication.\u003c/p\u003e\n\u003cp\u003eIn this post, we will use the Docker Model Runner to run Google\u0026rsquo;s Gemma AI model in our local machine with OpenAI, and create a spring boot AI chat application which will generate responses based on the previous context.\u003c/p\u003e","title":"Chatbot using Spring AI and OpenAI"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/classes/","section":"Tags","summary":"","title":"Classes"},{"content":"This post is about understanding the type of interview questions which can come from a Custom Java class where we can override equals, hashcode, *toString *or implements the Interface to *sort *the list of this custom Java class.\nLets go one by one, where you start your coding round and the first information provided is\n1. Create a Customer Java class which has Name, Age and Profession attributes. The interviewer also added override the *toString *method to display it in below format.\nname: \u0026#34;\u0026#34;, age: \u0026#34;\u0026#34;, profession: \u0026#34;\u0026#34; Customer.java\nclass Customer{ private String name; private int age; private String profession; Customer(String name, int age, String profession){ this.name = name; this.age = age; this.profession = profession; } public String getName() { return name;} public void setName(String name) { this.name = name;} public int getAge() { return age;} public void setAge(int age) { this.age = age;} public String getProfession() { return profession;} public void setProfession(String profession) { this.profession = profession;} @Override public String toString(){ return \u0026#34;name: \u0026#34;+this.name + \u0026#34; age: \u0026#34; + this.age + \u0026#34; profession:\u0026#34; + this.profession; } } 2. Create a service implementation which takes *Customer *class, create a Set and add the customer object by calling the addCustomer() method.\nclass CustomerServiceImpl { Set\u0026lt;Customer\u0026gt; customers = new HashSet\u0026lt;\u0026gt;(); public void addCustomer(Customer newCust){ customers.add(newCust); } } 3. From the main method, call this addCustomer() and insert two records with same name, but different age and profession. After that call the toString() method.\nCustomerServiceImpl impl = new CustomerServiceImpl(); impl.addCustomers(new Customer(\u0026#34;DynamicallyBlunt\u0026#34;, 10, \u0026#34;Developer\u0026#34;)); impl.addCustomers(new Customer(\u0026#34;DynamicallyBlunt\u0026#34;, 11, \u0026#34;Data Scientist\u0026#34;)); System.out.println(impl.customers); Here questions could be:\nHow many elements will be added to the Set ? Answer: Since it is a set, it won\u0026rsquo;t allow duplicate object in the set, but since the set elements are object and we have two different object (luckily having same name), there will be 2 elements in the customers set. If its not 1, then why is it happening so ? and Can we fix it ? Answer: Since the default object\u0026rsquo;s equals and hashCode is generated by including all the attributes of the custom class, it will create the hashCode of by combining all attributes and then does an equals on the entire object which will be different for both the records. Hence allowing to insert the second record with the same name. We can fix it, by overriding the *equals *and *hashcode *method as per business requirement. 4. Write the overridden methods which will not allow Customer object with same name to be inserted in the Set.\n@Override public int hashCode(){ return this.name.hashCode(); } @Override public boolean equals(Object obj){ Customer cObj = (Customer)obj; return this.getName().equalsIgnoreCase(cObj.getName()); } Here, another questions could be\nWhat is the change from the previous output ? Answer: This will stop the insertion of the second record in the Set with the same name, which answers the previous question of fixing it. What if another Customer object is added with same name, but having lowercase characters (name: \u0026lsquo;dynamicallyblunt\u0026rsquo;, age:11, profession: \u0026lsquo;Devops Engg\u0026rsquo;) ? Answer: Here, even though we have used equalsIgnoreCase() in our equals() method, still it will add this new record into the Set. Because the *hashCode *which it gets will be different for strings with different cases. We can test this by printing the *hashCode *of same strings with different cases (It could be different if executed on your machine. depends on the JVM) System.out.println(\u0026#34;dynamicallyblunttech\u0026#34;.hashCode()); 1630329008 System.out.println(\u0026#34;DynamicallyBluntTech\u0026#34;.hashCode()); -464150320 So, we have to either convert the name attribute used in hashCode() method to uppercase or to lowercase, such that if the strings is same, irrespective of the lowercase or uppercase, it will not insert into the Set. @Override public int hashCode(){ return this.name.toLowerCase(Locale.ROOT).hashCode(); } 5. Tell the output, if we use a sorted set instead of HashSet.\nclass CustomerServiceImpl { SortedSet\u0026lt;Customer\u0026gt; customers = new TreeSet\u0026lt;\u0026gt;(); public void addCustomers(Customer newCust){ customers.add(newCust); } } So, as asked, we are now using a *TreeSet *which is a sorted set implementation. And since the sorting behavior is not defined, it will throw an exception saying my custom Java class Customer.java is not implementing *Comparable *interface.\nThis will be the exact error message Error Now to fix this, we have to implement the *Comparable *Interface and provide the logic for its compareTo() method which takes one argument.\nclass Customer implements Comparable\u0026lt;Customer\u0026gt;{ private String name; . . . . @Override public int compareTo(Customer o) { return this.name.toLowerCase(Locale.ROOT).compareTo(o.getName()); } Using this, now my Sorted set will be sorted in ascending order by the ASCII code.\nFor the following records inserted\nimpl.addCustomers(new Customer(\u0026#34;dynamicallyBlunt\u0026#34;, 10, \u0026#34;Developer\u0026#34;)); impl.addCustomers(new Customer(\u0026#34;tech\u0026#34;, 11, \u0026#34;Devops\u0026#34;)); impl.addCustomers(new Customer(\u0026#34;com\u0026#34;, 11, \u0026#34;Data Scientist\u0026#34;)); We will get the following output\n[name: com age: 11 profession:Data Scientist, name: dynamicallyBlunt age: 10 profession:Developer, name: tech age: 11 profession:Devops] 6. Sort based on the customer age, If we are using List instead of Set.\nclass CustomerComparator implements Comparator\u0026lt;Customer\u0026gt;{ @Override public int compare(Customer o1, Customer o2) { if(o1.getAge() \u0026lt; o2.getAge()) return -1; else if (o1.getAge() \u0026gt; o2.getAge()) return 1; //Both scenario not matching then it is same age return 0; } } So, in this portion the interviewer is hinting to use *Comparator *interface instead of Comparable. Because you can sort based on any attribute using Comparator, and comparator can be implemented with Collections.sort(), or Arrays.sort() method.\nSo, for the following records inserted\nimpl.addCustomers(new Customer(\u0026#34;tech\u0026#34;, 11, \u0026#34;Devops\u0026#34;)); impl.addCustomers(new Customer(\u0026#34;com\u0026#34;, 11, \u0026#34;Data Scientist\u0026#34;)); impl.addCustomers(new Customer(\u0026#34;interview\u0026#34;, 9, \u0026#34;Developer\u0026#34;)); impl.addCustomers(new Customer(\u0026#34;dynamicallyBlunt\u0026#34;, 10, \u0026#34;Developer\u0026#34;)); Collections.sort(impl.customers, new CustomerComparator()); System.out.println(impl.customers); We will get the following output sorted based on the age in ascending order.\n[name: interview age: 9 profession:Developer, name: dynamicallyBlunt age: 10 profession:Developer, name: tech age: 11 profession:Devops, name: com age: 11 profession:Data Scientist] This concludes the set of basic interview questions on custom Java class and its different implementation and sorting ways which gets tricky once the interviewer ask to implement it during the coding round. Its always advisable to practice before presenting.\nHope this helps a bit in understanding the different techniques and the order of the questions that can come up. Do let me know if I am missing any other interview questions on Custom Java Class or if anything here can be enhanced.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/custom-java-class-peculiar-cases/","section":"Posts","summary":"\u003cp\u003eThis post is about understanding the type of interview questions which can come from a Custom Java class where we can override \u003cem\u003eequals\u003c/em\u003e, \u003cem\u003ehashcode\u003c/em\u003e, *toString *or implements the Interface to *sort *the list of this custom Java class.\u003c/p\u003e\n\u003cp\u003eLets go one by one, where you start your coding round and the first information provided is\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.\u003c/strong\u003e Create a Customer Java class which has Name, Age and Profession attributes. The interviewer also added override the *toString *method to display it in below format.\u003c/p\u003e","title":"Custom Java Class : Peculiar Cases"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/interview/","section":"Tags","summary":"","title":"Interview"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/memory/","section":"Categories","summary":"","title":"Memory"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/springai/","section":"Categories","summary":"","title":"SpringAI"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/springai/","section":"Tags","summary":"","title":"SpringAI"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/stream/","section":"Categories","summary":"","title":"Stream"},{"content":"In this post we will be creating a Web Socket Client which will receive all messages coming from the Web Socket Server.\nWeb Sockets #The web socket is a communication protocol like HTTP used in client - server architecture. But, unlike HTTP, web-sockets are bi-directional, full duplex communication protocol. This means that the data can be flown in either directions, client to server or server to client.\nWeb Sockets starts with either ws:// or wss://\nIt is state-ful protocol, which means the communication between client and server is kept alive for the entire duration until it is closed by either client or server.\nTransmissions Web Socket Server #I received a task to capture stream of messages send from Server and we have to read each message and do some post validations on those messages and may be store the data in database or in-memory. This post will be about receiving the messages from server and printing it on the console by the client.\nI have the server which is running on the port 8032. This is some jar file which cannot be changed. It send data in JSON format which describes the product id (isin), a little description about it and the event type(type) whether to add it to our list or delete it. It looks something like this\n{ \u0026#34;data\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;impetus tellus appareat movet\u0026#34;, \u0026#34;isin\u0026#34;: \u0026#34;OV5407757082\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;ADD\u0026#34; } Web Socket Client #We will be using javax.websocket package for creating the client. Also, we will annotate the client class with ***@***ClientEndpoint which will denotes that this class is my Web Socket client and will have the web socket lifecycle events.\nIn order to create a Web Socket client, we need to create a Web Socket container which will provide helpful methods to connect to the server. To connect to the server, like any rest API, here also we will have URLs which will be called by the client class.\nTo create the container, we have ContainerProvider abstract* ***class provided by the javax.websocket package. It has getWebSocketContainer() method which creates a *WebSocketContainer *instance using the org.apache.tomcat.websocket.WsWebSocketContainer class.\nRefer to the code for *ContainerProvider.getWebSocketContainer() *method from the abstract class from Java 8.\npublic static WebSocketContainer getWebSocketContainer() { WebSocketContainer result = null; ServiceLoader\u0026lt;ContainerProvider\u0026gt; serviceLoader = ServiceLoader.load( ContainerProvider.class); for( Iterator iter = serviceLoader.iterator(); result == null \u0026amp;\u0026amp; iter.hasNext(); result = ((ContainerProvider)iter.next()).getContainer() ) {} if (result == null) { try { Class\u0026lt;WebSocketContainer\u0026gt; clazz = Class.forName( \u0026#34;org.apache.tomcat.websocket.WsWebSocketContainer\u0026#34;); result = (WebSocketContainer)clazz .getConstructor() .newInstance(); } catch (IllegalArgumentException | SecurityException | ReflectiveOperationException var4) { } } return result; } Once you have the container instance, we can call the connectToServer() method with two arguments, one is the instance this and other is the server URL exposed.\nYou need to handle the *DeploymentException *and *IOException *here.\npublic MyWebSocketClientEndpoint(URI uri){ WebSocketContainer container = ContainerProvider .getWebSocketContainer(); try { container.connectToServer(this, uri); } catch (DeploymentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } Lifecycle Methods #Now as part of the client, there are three phases for every connection\nonOpen - what needs to be done, when we open the connection onClose - what needs to be done, when we close the connection onMessage - what needs to be done, when we receive the messages from the server during the session. So, as part of the every Web Socket Client lifecycle methods, we will implement these three phases.\nNOTE: You might have noticed, that I have written phases and not methods, because we can provide any method name here. To recognize the phase we need to annotate the respective methods with ***@***onOpen, or ***@***onClose, or ***@***onMessage annotation.\nYou can have any implementation in these methods, but I will just print the name and messages on the console as part of this post.\n@OnOpen //Method Name can be anything public void onOpen(Session session){ System.out.println(\u0026#34;On Open\u0026#34;); this.session = session; } @OnClose //Method Name can be anything public void onClose(){ System.out.println(\u0026#34;On Close\u0026#34;); this.session = null; } @OnMessage //Method Name can be anything public void onMessage(String message){ System.out.println(\u0026#34;On Message:\u0026#34;+ message); } To test this, we can call out *MyWebSocketClientEndpoint *class and provide the correct URL, which will start the connection to the server. If you want to keep on receiving the messages from the server and keep the connection open, use Thread.sleep() to keep the connection alive. Else it will close the socket connection with the server after few messages only. Providing the thread to sleep, allows the message stream to be printed on the console.\nYou can refer to my github projects here and refer to this class MyWebSocketClientEndpoint.java for the entire code.\nHope this helps others also. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/web-socket-client-receiving-stream-of-messages/","section":"Posts","summary":"\u003cp\u003eIn this post we will be creating a Web Socket Client which will receive all messages coming from the Web Socket Server.\u003c/p\u003e\n\u003ch3 id=\"web-sockets\" class=\"relative group\"\u003e\u003cstrong\u003eWeb Sockets\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#web-sockets\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eThe web socket is a communication protocol like HTTP used in client - server architecture. But, unlike HTTP, web-sockets are \u003cem\u003e\u003cstrong\u003ebi-directional, full duplex\u003c/strong\u003e\u003c/em\u003e communication protocol. This means that the data can be flown in either directions, client to server or server to client.\u003c/p\u003e","title":"Web Socket Client - Receiving Stream of Messages"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/websocket/","section":"Tags","summary":"","title":"WebSocket"},{"content":"Part 2 of the StreamMetrics Series\n*Previous parts: Part 1: Kafka Producer | *Part 3: Real-time Aggregation * | * Part 4: From Localhost to Kubernetes\nA deep-dive into manual offset management, dead letter queues, and observability with Spring Boot + Apache Kafka. This is part 2 of the series Stream Metrics application. *In Part 1 we built the producer, today we add the consumer.\nWhy This Matters #Most tutorials show you how to build a Kafka consumer. They show you @KafkaListener, they show you how to read a message, and they call it production-ready. It is not!\nProduction Kafka consumers need to answer hard questions:\nWhat happens when your database is down and you can\u0026rsquo;t process a message? What happens when a malformed event enters your topic? How do you know if your consumer is falling behind? How do you trace a message from producer to consumer? This post answers all of those questions with code from StreamMetrics, a real-time analytics platform I built to process 10,000+ events per second from multiple microservices.\nArchitecture Overview\nBefore diving into code, here is the data flow StreamMetrics uses: Archiecture Overview The consumer group has 3 threads, each owning 2 partitions from the 6-partition topic. This gives us parallelism while maintaining partition-level ordering guarantees.\n1. Manual Offset Management: Why Auto-Commit Will Eventually Burn You #Spring Kafka\u0026rsquo;s default is enable.auto.commit=true, which commits offsets every 5 seconds regardless of whether your processing succeeded. This creates a silent data loss scenario that is extremely hard to debug in production.\nThe Problem with Auto-Commit\nConsider this sequence of events with auto-commit enabled:\nConsumer polls 500 records from Kafka Auto-commit timer fires at 3rd second, commits offset for all 500 records At second 4, your database goes down, processing failed for records 301-500 Consumer restarts - but offsets are already committed Records 301-500 are silently lost forever. Auto-commit is optimistic. Manual offset management is pessimistic. For financial metrics, audit logs, or any data you cannot afford to lose, always use manual commits.\nConfiguration: ## application.yml spring: kafka: consumer: enable-auto-commit: false # Manual control auto-offset-reset: earliest # Start from beginning on # new group max-poll-records: 500 # Batch size per poll max-poll-interval-ms: 300000 # 5 min max processing time session-timeout-ms: 10000 # Broker considers consumer # dead after 10s heartbeat-interval-ms: 3000 # Send heartbeat every 3s isolation-level: read_committed # Only read committed transactions max-poll-records: 500 — This is your throughput knob.\nHigher = better throughput, lower = faster failure recovery. At 500, if processing fails, you re-process at most 500 records. isolation-level: read_committed — Critical if your producers use transactions. Without this, you will read uncommitted messages that might be rolled back.\nsession-timeout-ms: 10000 — If your consumer does not send a heartbeat for 10 seconds, the broker triggers a rebalance. Set this higher than your max processing time for a single batch.\nThe Listener Container Configuration #@Bean public ConcurrentKafkaListenerContainerFactory\u0026lt;String, MetricEvent\u0026gt; kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory\u0026lt;String, MetricEvent\u0026gt; factory = new ConcurrentKafkaListenerContainerFactory\u0026lt;\u0026gt;(); factory.setConsumerFactory(consumerFactory()); factory.setConcurrency(3); // 3 threads for 6 partitions = 2 partitions/thread // CRITICAL: Manual acknowledgment mode factory.getContainerProperties() .setAckMode(ContainerProperties.AckMode.MANUAL); return factory; Committing Offsets in the Listener #@KafkaListener( topics = \u0026#34;${app.kafka.topic.metrics-events}\u0026#34;, groupId = \u0026#34;${spring.kafka.consumer.group-id}\u0026#34;, containerFactory = \u0026#34;kafkaListenerContainerFactory\u0026#34; ) public void consumeMetric( @Payload ConsumerRecord\u0026lt;String, MetricEvent\u0026gt; record, @Header(KafkaHeaders.RECEIVED_PARTITION) int partition, @Header(KafkaHeaders.OFFSET) long offset, Acknowledgment acknowledgment) { // Injected by Spring MetricEvent event = record.value(); try { metricProcessor.process(event); // Only commit AFTER successful processing acknowledgment.acknowledge(); } catch (Exception e) { // Send to DLQ, then commit to avoid poison pill sendToDeadLetterQueue(event, e.getMessage(), \u0026#34;PROCESSING_ERROR\u0026#34;); acknowledgment.acknowledge(); } } Key insight: Even when processing fails, we commit the offset after sending to DLQ. This prevents a single bad message (poison pill) from blocking the entire partition forever.\n2. The Dead Letter Queue Pattern #A Dead Letter Queue (DLQ) is where messages go when they cannot be processed.\nWithout a DLQ, you have two terrible options:\ndrop the message (data loss) or retry forever (partition stall). The DLQ gives you a third option: park the message safely, keep the main pipeline moving, and investigate later.\nTwo Categories of Failures #Validation Failures\nThe message arrived but is semantically wrong. A null serviceId, a negative metric value, a missing required field. These will never succeed no matter how many times you retry. Send immediately to DLQ.\nProcessing Failures\nThe message is valid but something downstream failed. Redis is unavailable, database connection timed out, network blip. These might succeed on retry - but for simplicity in this implementation, we also send to DLQ and handle retry separately.\nValidation Before Processing #// Validate using Jakarta Bean Validation Set\u0026lt;ConstraintViolation\u0026lt;MetricEvent\u0026gt;\u0026gt; violations = validator.validate(event); if (!violations.isEmpty()) { String errorMsg = \u0026#34;Validation failed: \u0026#34; + violations.toString(); log.error(\u0026#34;Invalid metric event: eventId={}, errors={}\u0026#34;, event.getEventId(), errorMsg); sendToDeadLetterQueue(event, errorMsg, \u0026#34;VALIDATION_ERROR\u0026#34;); acknowledgment.acknowledge(); // Commit and move on return; } The DLQ Sender #private void sendToDeadLetterQueue( MetricEvent event, String reason, String errorType) { // Enrich event with failure metadata before sending to DLQ Map\u0026lt;String, String\u0026gt; metadata = event.getMetadata(); if (metadata == null) { metadata = new HashMap\u0026lt;\u0026gt;(); event.setMetadata(metadata); } metadata.put(\u0026#34;dlq_reason\u0026#34;, reason); metadata.put(\u0026#34;dlq_error_type\u0026#34;, errorType); metadata.put(\u0026#34;dlq_timestamp\u0026#34;, String.valueOf(System.currentTimeMillis())); metadata.put(\u0026#34;dlq_original_topic\u0026#34;, inputTopic); deadLetterProducer .send(deadLetterTopic, event.getEventId(), event) .whenComplete((result, ex) -\u0026gt; { if (ex == null) { log.info(\u0026#34;Sent to DLQ: eventId={}\u0026#34;, event.getEventId()); } else { // Critical: DLQ itself failed log.error(\u0026#34;DLQ send failed: eventId={}\u0026#34;, event.getEventId(), ex); } }); } Always enrich DLQ messages with failure metadata before sending. When you investigate failures 3 days later, you need to know WHY the message failed, WHEN it failed, and WHERE it came from.\nMonitoring Your DLQ #A growing DLQ is a production alert. In Kafka UI (or any Kafka management tool), watch the message count on your dead-letter topic. If it grows faster than your team can investigate, you have a systemic problem.\nIn StreamMetrics, we track this with a custom Micrometer counter:\n// Increment on every DLQ send Counter.builder(\u0026#34;metrics.failed\u0026#34;) .tag(\u0026#34;service\u0026#34;, event.getServiceId()) .tag(\u0026#34;reason\u0026#34;, errorType) .register(meterRegistry) .increment(); 3. Observability: You Cannot Fix What You Cannot See #The observability stack in StreamMetrics has three layers:\nmetrics (what happened), tracing (how it happened), and logging (why it happened). This section focuses on metrics and tracing since those are the hardest to set up correctly with Kafka.\nThe Stack\nMicrometer — metrics facade (same API regardless of backend) Prometheus — metrics storage and query engine Grafana — dashboards and alerting Zipkin — distributed tracing (optional, gracefully degraded) Spring Boot Actuator Configuration ## application.yml management: endpoints: web: exposure: include: health,info,metrics,prometheus metrics: export: prometheus: enabled: true tags: application: ${spring.application.name} distribution: percentiles-histogram: http.server.requests: true percentiles: http.server.requests: 0.5, 0.95, 0.99 The tags.application tag is critical. Without it, all your metrics from different services look identical in Prometheus. Always tag with the application name.\nThe Key Apache Kafka Metrics #After starting both producer and consumer, hit /actuator/prometheus and look for these:\n# Producer: How many messages sent and at what latency spring_kafka_template_seconds_count{result=\u0026#34;success\u0026#34;} spring_kafka_template_seconds_max # Consumer: Records consumed per second kafka_consumer_records_consumed_total # Circuit breaker state (if using Resilience4j) resilience4j_circuitbreaker_state resilience4j_circuitbreaker_calls_seconds_count Grafana Queries That Actually Matter #These are the three panels on the StreamMetrics dashboard:\n# Panel 1: Producer throughput (messages/sec) rate(spring_kafka_template_seconds_count{result=\u0026#34;success\u0026#34;}[1m]) # Panel 2: Max send latency spring_kafka_template_seconds_max # Panel 3: JVM heap pressure jvm_memory_used_bytes{area=\u0026#34;heap\u0026#34;} Distributed Tracing: Making Tracer Optional #One lesson learned the hard way: never make your critical path depend on your observability infrastructure. If Zipkin is down, your Kafka producer should still work. Make the Tracer optional:\n@Service @RequiredArgsConstructor public class MetricProducerService { private final KafkaTemplate\u0026lt;String, MetricEvent\u0026gt; kafkaTemplate; private final Optional\u0026lt;Tracer\u0026gt; tracer; // Optional - won\u0026#39;t fail if Zipkin is down public CompletableFuture\u0026lt;SendResult\u0026lt;String, MetricEvent\u0026gt;\u0026gt; sendMetric(MetricEvent event) { // Only add trace context if tracer is available tracer.ifPresent(t -\u0026gt; { if (t.currentSpan() != null) { String traceId = t.currentSpan().context().traceId(); event.getMetadata().put(\u0026#34;traceId\u0026#34;, traceId); } }); return kafkaTemplate.send(topic, event.getServiceId(), event); } } This is a general principle: observability tools should observe your system, not be a dependency of it. If your tracing library throws an exception and crashes your producer, you have made your system less reliable in the name of observability.\n4. Hard-Won Lessons from Building This #Kafka Docker Listener Configuration Is Unintuitive #The single biggest time sink in this project was Kafka\u0026rsquo;s listener configuration in Docker. When running Kafka in Docker and your Spring Boot app on the host machine, you need two separate listeners: one for internal broker-to-broker communication, and one for external clients.\n# docker-compose.yml — The config that finally worked KAFKA_LISTENERS: \u0026#39;PLAINTEXT://kafka1:19092,EXTERNAL://0.0.0.0:9092,CONTROLLER://kafka1:9093\u0026#39; KAFKA_ADVERTISED_LISTENERS: \u0026#39;PLAINTEXT://kafka1:19092,EXTERNAL://localhost:9092\u0026#39; The PLAINTEXT listener (port 19092) handles broker-to-broker traffic inside the Docker network. The EXTERNAL listener (port 9092) is what your Spring Boot app connects to via localhost. Both are necessary.\nStale Kafka Data Causes Mysterious Errors #After changing Kafka configuration, always delete the data directories before restarting. Old cluster metadata causes DUPLICATE_BROKER_REGISTRATION errors that are confusing if you do not know what to look for.\ndocker-compose down rm -rf kafka1/data kafka2/data kafka3/data docker-compose up -d Multi-Module Maven Requires Careful Ordering #In a multi-module Maven project, the parent POM\u0026rsquo;s module list determines build order. Common module must be listed first, or the producer will fail to find MetricEvent even though the dependency is declared correctly.\n5. Results #After implementing all of the above, StreamMetrics processes over 10,000 events per second through a 3-broker Kafka cluster with:\nZero message loss (verified by tracking event IDs end to end) Failed messages safely parked in DLQ with full failure context Real-time throughput and latency visible in Grafana Optional distributed tracing through Zipkin Manual offset commits ensuring exactly-once processing semantics Graphana Metrics Kafka UI with 30k Messages What\u0026rsquo;s Next #Part 3 will cover Kafka Streams for real-time aggregations: computing 1-minute and 5-minute rolling averages of metric values, handling late-arriving data, and storing aggregated results back to Kafka and Redis.\nYou can find my:\nGithub: StreamAnalytics Instagram: backendbrilliance Medium: Link Share your thoughts whether you liked or disliked it. Do let me know if you having any queries or suggestions. Never forget, Learning is the primary goal.\nLearn with every commit.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/building-production-grade-kafka-consumer-patterns/","section":"Posts","summary":"\u003cp\u003e\u003cstrong\u003ePart 2 of the StreamMetrics Series\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e*Previous parts: \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-a-production-kafka-producer/\"\u003e\u003cem\u003ePart 1: Kafka Producer\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/real-time-aggregations-with-kafka-streams-at-10k-events-sec/\"\u003e*Part 3: Real-time Aggregation *\u003c/a\u003e\u003c/em\u003e | *\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/from-localhost-to-kubernetes-deploying-streammetrics-at-scale/\"\u003e\u003cem\u003ePart 4: From Localhost to Kubernetes\u003c/em\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eA deep-dive into manual offset management, dead letter queues, and observability with Spring Boot + Apache Kafka. This is part 2 of the series Stream Metrics application. *In \n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-a-production-kafka-producer/\"\u003ePart 1\u003c/a\u003e we built the producer, today we add the consumer.\u003c/p\u003e\n\u003ch4 id=\"why-this-matters\" class=\"relative group\"\u003e\u003cstrong\u003eWhy This Matters\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#why-this-matters\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003cp\u003eMost tutorials show you how to build a Kafka consumer. They show you @KafkaListener, they show you how to read a message, and they call it production-ready. It is not!\u003c/p\u003e","title":"Building Production-Grade Apache Kafka Consumer Patterns"},{"content":"This post will only includes scenario based Kubernetes problem and the commands/debugging steps. It will be updated whenever I learn something new.\nFor Installation of Kubernetes refer here: https://kubernetes.io/docs/tasks/tools/\nCase 1: Get all pods in a namespace #kubectl get pods -n \u0026lt;namespace_name\u0026gt; Case 2: Get all namespaces #kubectl get namespaces OR kubectl get ns Case 3: Create pods with a name and image in a particular namespace #kubectl run \u0026lt;pod_name\u0026gt; --image=\u0026lt;image_name\u0026gt; -n \u0026lt;namespace\u0026gt; e.g.\nkubectl run redis --image=redis -n my-namespace Case 4: Get all pods in all the namespaces #kubectl get pods --all-namespaces Case 5: Get all replicasets #kubectl get replicaset Case 6: Create pods using yaml file #kubectl apply -f abc.yml -n namespace abc.yaml or abc.yml* file: apiVersion: v1 kind: Pod metadata: name: new-pod label: type: linux spec: containers: -name: redis image: redis Case 7: Create deployment using yaml file #kubectl apply -f deployment.yml deployment.yml file is having\napiVersion: apps/v1 kind: Deployment metadata: name: new-deployment spec: replicas: 3 selector: matchLabels: name: abc-pod template: metadata: labels: name: abc-pod spec: containers: - name: abc-container image: nginx command: - sh - \u0026#34;-c\u0026#34; - echo Hello Kubernetes! \u0026amp;\u0026amp; sleep 3600 Here important points are:\napiVersion should follow the way apps/v1 or apps/v kind is case sensitive matchlabels.name should be same as metadata.labels.name You can specify the replica number and that many pods will be created. When this deployment will be created, a replica set, 3 pods, one deployment will be created. Case 8: Get all details of the pod #kubectl describe pod \u0026lt;pod_name\u0026gt; -n \u0026lt;namespace\u0026gt; Case 9: Get all pods, replicaset, deployment and services present #kubectl get all Case 10: Editing an existing replicaset #kubectl edit replicaset \u0026lt;replicaset_name\u0026gt; There will be an another post soon dealing with more kubernetes day to day used commands.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/kubernetes-learning-phase-1-basic-commands/","section":"Posts","summary":"\u003cp\u003eThis post will only includes scenario based Kubernetes problem and the commands/debugging steps. It will be updated whenever I learn something new.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eFor Installation of Kubernetes refer here\u003c/strong\u003e: \u003ca href=\"https://kubernetes.io/docs/tasks/tools/\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://kubernetes.io/docs/tasks/tools/\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"case-1-get-all-pods-in-a-namespace\" class=\"relative group\"\u003eCase 1: Get all pods in a namespace \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#case-1-get-all-pods-in-a-namespace\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ekubectl get pods \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003en \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003enamespace_name\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"case-2-get-all-namespaces\" class=\"relative group\"\u003eCase 2: Get all namespaces \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#case-2-get-all-namespaces\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ekubectl get namespaces \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOR\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ekubectl get ns\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"case-3-create-pods-with-a-name-and-image-in-a-particular-namespace\" class=\"relative group\"\u003eCase 3: Create pods with a name and image in a particular namespace \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#case-3-create-pods-with-a-name-and-image-in-a-particular-namespace\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ekubectl run \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epod_name\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eimage\u003cspan style=\"color:#f92672\"\u003e=\u0026lt;\u003c/span\u003eimage_name\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003en \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003enamespace\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cem\u003ee.g.\u003c/em\u003e\u003c/p\u003e","title":"KUBERNETES LEARNING PHASE 1 - BASIC COMMANDS"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/agents/","section":"Categories","summary":"","title":"Agents"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/agents/","section":"Tags","summary":"","title":"Agents"},{"content":"Spring provides a simple and powerful way of writing custom wrappers function which can be executed around a range of methods. Aspect-oriented programming complements Object-oriented programming by providing another way of thinking about program structure.\nThe key unit of modularity in Aspects oriented programming (AOP) is the Aspects. Aspects enables modularization of concerns that cut across multiple types and objects. Such concerns are termed as \u0026ldquo;cross-cutting\u0026rdquo; concerns.\nAOP is used in Spring framework to:\nProvide declarative enterprise services (e.g. declarative transaction management) Implements custom aspects. The above details are directly taken from the Spring documentation. When we talk about the modularity, it means how each functional unit behaves. Aspects-oriented programming allow developers to do specific things around these functional unit in order to add more value to its behavior. Logging is a very common example, where I want to add logging to all the methods before or after or around its execution. Transaction management is also one such example.\nWe will use Logging as an example in this post to explore more on this feature. Before that, let\u0026rsquo;s look into few more terminology that is used in Aspect-oriented programming.\nNow, it is evident that we have three important items when we want to execute something around some functionality.\nFirst is my code where I want to add the extra functionality (cross-cutting concerns). This is know as #JoinPoint. Second is the selection criteria to select the #Joinpoint to apply that cross-cutting concerns. Third is the reusable code which I want to implement when the #Joinpoint is recognized. This is known as #Advice. Now lets look into the official definition of these terms:\nAspects: A modularization of a concern that cut across multiple classes. In Spring AOP, aspects are implemented by using regular classes (the scheme-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style.) Join point: A point during the execution of a program, such as execution of a method or handling of exception. In Spring AOP, a join point always represent method execution. Advice: Action taken by an aspect at a particular join point. Different types of advice include \u0026ldquo;around\u0026rdquo;, \u0026ldquo;before\u0026rdquo; and \u0026ldquo;after\u0026rdquo; advice. Pointcut: A predicate that matches the join point. Weaving: linking aspects with other application types or objects to create and advised object. This can be done at compile time (using AspectJ compiler), load time, or at runtime. The weaving is done at byte-code level and called byte-code weaving as well. ** AOP Proxies** # Spring AOP default to use standard JDK dynamic proxies for AOP proxies. This enables all the interface can be proxied. Spring AOP can also use CGLIB proxies which makes the classes rather than interfaces. Lets look into the basics implementation of creating any Aspects.\nFirst and Foremost is to enable AspectJ in you spring application. In this example we are taking the spring-core libraries only with AspectJ dependency.\nDependencies #So, the pom will look like:\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${spring.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-context\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${spring.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.aspectj\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;aspectjweaver\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.9.19\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Additionally, we will add the log4j libraries to enable logging for all methods.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-log4j12\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.7\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; These are all the dependencies required for our Spring core project.\nConfiguration #In order to enable AspectJ and load the configuration we will create a configuration class.\nimport org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.PropertySource; @PropertySource(value = \u0026#34;classpath:application.properties\u0026#34;) @ComponentScan(basePackages = \u0026#34;org.aa\u0026#34;) @Configuration @EnableAspectJAutoProxy public class AppConfig {} The above code\nsets the configuration files location sets the base packages to recognize the components. enable AspectJ proxy. Aspects #To enable logging for all the method execution, we will be creating Logging interface which will be used over method as annotation where we want to enable logging.\nimport java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable {} In the above code, we are creating custom annotation for logging purpose which sets\nif @Target meta-annotation is present, then compiler will restrict the usage based on the *ElementType *enum, in this case it is methods. @Retention annotation indicates how long the annotation with the annotated method to be retained. Now we will be creating the *LoggingAspect *using the Logging annotation which we created above. In this class we will create different types of Advices and also see the order of the execution.\n@Component @Aspect public class LoggingAspect{ private static final Logger LOG = LoggerFactory.getLogger(LoggingAspect.class); @Pointcut(\u0026#34;@annotation(Loggable)\u0026#34;) public void executeLogging(){} . . . We have to annotate the LoggingAspect class with the @Aspect annotation to use this as the aspect class for our logging objective.\nIn the above code, we have defined our Pointcut operation which are method annotated with the annotation Loggable, there by separating the concerns.\nServices #For this task, we have two services created, where one service calls another service.\nA1Service.java #@Service public class A1Service { B1Service b1Service; public A1Service(B1Service b1Service){ this.b1Service = b1Service; } @Loggable public String serviceClass(){ System.out.println(\u0026#34;A1 service class method\u0026#34;); return \u0026#34;A1\u0026#34;; } @Loggable public String serviceSubClass() { System.out.println(\u0026#34;A1 service sub class calling B1\u0026#34;); return serviceClass()+b1Service.serviceSubClass(); } } B1Service.java #@Service public class B1Service { public String serviceClass(){ return \u0026#34;B1\u0026#34;; } public String serviceSubClass(){ return \u0026#34;-b1\u0026#34;; } } If you observe, we have @Loggable added on the A1Service methods while the B1Service is not bounded by the aspects.\nMain #In my main method, we are calling the serviceSubClass() method of A1Service odd number of times in the range 1 to 10.\npublic class Main { public static void main( String[] args ) { ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); B1Service b1 = ac.getBean(B1Service.class); A1Service a1 = ac.getBean(A1Service.class); IntStream .rangeClosed(1, 10) .filter(x -\u0026gt; x%2!=0) .forEach(x -\u0026gt; System.out.println(x+\u0026#34;-\u0026#34;+ a1.serviceSubClass())); } } We are using *AnnotationConfigApplicationContext *to get the service beans.\nNext, we will implement different Advices.\n@Before #This advice will run before the execution of the method starts.\n. . @Before(\u0026#34;executeLogging()\u0026#34;) public void logCall(JoinPoint jp){ LOG.info(\u0026#34;Before-\u0026#34;+jp.getSignature().getName()); } . . In continuation with the above code sample, this method will be added with @Before advice annotation which tells the Pointcut expression where to bind the object. In this case it is executeLogging() method.\nSo before executing the method, it will execute this advice. Execution Order If you see the order, before executing the method, we have the LoggingAspect log in the run console.\n@After #Unlike @Before, this advice will be executed after the method execution is done. The important aspect to consider is that the advice will run irrespective of whether an exception occurred during the method execution.\n@After(\u0026#34;executeLogging()\u0026#34;) public void logAfterCall(JoinPoint jp){ LOG.info(\u0026#34;After-\u0026#34;+jp.getSignature().getName()); } We can see from the logs that it is executed after the method execution Execution Order @After has two more variation to it, @AfterThrowing and @AfterReturning. As the name suggests, @AfterThrowing advice will get executed if the method is throwing any exception, and @AfterReturning advice will get executed if the method is returning something.\n@AfterRetuning #@AfterReturning(\u0026#34;executeLogging()\u0026#34;) public void logAfterReturnCall(JoinPoint jp){ LOG.info(\u0026#34;After Returning-\u0026#34;+jp.getSignature().getName()); } This will have following logs Logs @AfterThrowing #We have to change the serviceSubClass() to throw *NoSuchFileException *for this to work.\n@AfterThrowing(\u0026#34;executeLogging()\u0026#34;) public void logAfterThrowingCall(JoinPoint jp){ LOG.info(\u0026#34;After Exception-\u0026#34;+jp.getSignature().getName()); } This will have following logs Logs @Around #The @Around annotation will bring a new variant where the ProceedingJoinPoint exposes proceed() method to support @Around advice execution.\nBefore the proceed() call, the advice is executed, and\non the proceed() call, it will execute the method and return an Object\nAfter the proceed() call, the remaining advice part is executed.\nYou can see the advice implementation\n@Around(value = \u0026#34;executeLogging()\u0026#34;) public Object logMethodCall(ProceedingJoinPoint pjp) throws Throwable { long startTime = System.currentTimeMillis(); LOG.info(\u0026#34;before calling proceed\u0026#34;); //method return value Object returnValue = pjp.proceed(); long runTimeForMethod = System.currentTimeMillis() - startTime; LOG.info(\u0026#34;Time to execute \u0026#34;+ pjp.getSignature().getName() + \u0026#34; is \u0026#34;+ runTimeForMethod +\u0026#34; ms\u0026#34;); return returnValue; } and the logs will be evident to tell how the order of execution will be. Execution Order This all on the Spring AOP implementation. This time all the code is present in this post and will update this, once all my Github check-in\u0026rsquo;s are done.\nDo comment out and like if you like the post or wanted more details on its working, or if any modification is to be done. It will be helpful as a community to grow.\nReferences # https://docs.spring.io/spring-framework/reference/core/aop/using-aspectj.html https://www.linkedin.com/learning/spring-spring-mvc-2 ","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/aspect-programming-in-spring-logging/","section":"Posts","summary":"\u003cp\u003eSpring provides a simple and powerful way of writing custom wrappers function which can be executed around a range of methods. Aspect-oriented programming complements Object-oriented programming by providing another way of thinking about program structure.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe key unit of modularity in Aspects oriented programming (AOP) is the  Aspects. Aspects enables modularization of concerns that cut across multiple types and objects. Such concerns are termed as \u0026ldquo;cross-cutting\u0026rdquo; concerns.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAOP is used in Spring framework to:\u003c/p\u003e","title":"Aspect Programming in Spring - Logging"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/aspects/","section":"Tags","summary":"","title":"Aspects"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/covid/","section":"Tags","summary":"","title":"Covid"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/cowin/","section":"Tags","summary":"","title":"Cowin"},{"content":"In this post we will be looking into the CoWIN APIs which GoI has published during the covid period where the third party app can utilize the APIs to create there own app.\nWe used only few API from the public list and created a Spring boot application which will get me the list of all the available slots for either the old or young people based on their age on a given date and list of pin codes.\nYou can refer to the government API from here: https://apisetu.gov.in/public/api/cowin#/\nAlso, you can find the Github link for all these services and the application at the end of this post.\nServices #We have three services which will be listed here with its descriptions:\n1. Get all available slots on a given date for given pincode area #Service Name: getAvailabilityByPinAndDate\nInput: Pincode, Date\nOutput: Sessions object\nDescription: Get all the available slots on a given date for a given pincode.\nEndpoint: cowin/pin=pincode1/date=date1\ne.g.: http://localhost:8083/cowin/pin=221001/date=07-05-2021 Sample Output: Output 2. Get all available slots for Young people on a given date and for given list of pincodes #Service Name: getAvailabilityByPinAndDateForYoung Input: List of Pincodes, Date Output: Sessions object Description: Get the available slots for Young (age between 18 to 45) on a given date for a list of given pincodes. Endpoint: cowin/young/pins=pincode1,pincode2,pincode3/date=date1\ne.g.: http://localhost:8083/cowin/young/pins=431801,431802,431803/date=07-05-2021 Sample Output: Output 3. Get all available slots for Old people on a given date and for given list of pincodes #Service Name: getAvailabilityByPinAndDateForOld Input: List of Pincodes, Date Output: Sessions object Description: Get the available slots for Old(age 45+) on a given date for a list of given pincodes. Endpoint: cowin/old/pins=pincode1,pincode2,pincode3/date=date1 e.g.: http://localhost:8083/cowin/old/pins=221001,221002,221003,221004,221005/date=07-05-2021 Sample Output: Output Github Link: https://github.com/ankitagrahari/CowinCo\nYou can find the Model classes for models which are made public by government. I will try other services as well and will update the same Github repository.\nIf you find the post helpful, do give a thumbs up and also share your feedback and topics that I should covered.\nIf you are looking for some COVID19 resources, follow this post COVID19-Resources for all the websites/application or twitter search format\nYour browser does not support the video tag. ","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/exploring-government-s-cowin-apis/","section":"Posts","summary":"\u003cp\u003eIn this post we will be looking into the CoWIN APIs which GoI has published during the covid period where the third party app can utilize the APIs to create there own app.\u003c/p\u003e\n\u003cp\u003eWe used only few API from the public list and created a \u003cem\u003e\u003cstrong\u003eSpring boot application\u003c/strong\u003e\u003c/em\u003e which will get me the list of all the available slots for either the old or young people based on their age on a given date and list of pin codes.\u003c/p\u003e","title":"Exploring Government's Cowin APIs"},{"content":"In this post we will create tokens of the given string using Apache OpenNLP library.\nThis post is a continuation of the previous post Find Sentences using Apache Open NLP**. **Refer to this post first for the setting up your project with Apache OpenNLP library. In the previous post we used SentenceDetecterME, SentenceModel, and ***Span ***class of the openNLP library.\nApache OpenNLP library provides the SimpleTokenizer, ***WhitespaceTokenizer ***and ***TokenizerME ***which will use the normal process to tokenize by splitting the string either on whitespace or using some already trained models. These classes doesn\u0026rsquo;t expose their constructors, so we have to use ***INSTANCE ***to get the instance of these classes.\nSimpleTokenizer #This will use split the sentences into each word or any exclamation, dot, comma etc used. It provides two methods to tokenize a given string. We will try both the methods.\nFirst step is to get the instance of the SimpleTokenizer //Get the Simple Tokenizer Instance SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; Use this object to get the tokenize() method which will take the string as input. //Call the tokenize method String[] tokens = tokenizer.tokenize(text); Print the tokens and the output will be //Print the tokens System.out.println(Arrays.toString(tokens)); Hi . How are you ? Hope everything is going well . Welcome to ChatBot Lesson1 . We will try to understand Open Apache NLP for sentence detection There is another tokenizePos() method which will print the position of the tokens. Its return type is an array of Span object. Span[] spans = tokenizer.tokenizePos(text); for(Span s: spans){ System.out.println(s); } The output of the above statement will be like this: [0..2) [2..3) [4..7) [8..11) [12..15) [15..16) Whitespace Tokenizer #This tokenizer will split the sentence based on white space. It also has two methods to returns tokens as string array and another tokenizePos() method which return an array of Span objects.\nHere is the code to get the ***WhiteSpaceTokenizer ***instance and printing the tokens and their position.\n//Get the Simple Tokenizer Instance WhitespaceTokenizer tokenizer = WhitespaceTokenizer.INSTANCE; //Call the tokenize method, this will split based on spaces String[] tokens = tokenizer.tokenize(text); //Print the tokens for(String s: tokens){ System.out.println(s); } Span[] spans = tokenizer.tokenizePos(text); for(Span s: spans){ System.out.println(s); } The output will be like this:\nHi. How are you? Hope everything is going well. Welcome to ChatBot Lesson1. We will try to understand Open Apache NLP for sentence detection [0..3) [4..7) [8..11) [12..16) [17..21) [22..32) [33..35) [36..41) [42..47) [48..55) [56..58) [59..66) [67..75) [76..78) [79..83) [84..87) [88..90) [91..101) [102..106) [107..113) [114..117) [118..121) [122..130) [131..140) The implementation is similar to the SimpleTokenizer.\nTokenizerME #This class will create tokens based on the trained model it is using. It loads a particular model to TokenizerModel and then loads it to this class.\nLoad the trained model into TokenizerModel object. I have used an existing model which comes with Apache OpenNLP library. //Load the token models InputStream is = new FileInputStream(\u0026#34;src\\\\main\\\\resources\\\\models\\\\en-token.bin\u0026#34;); TokenizerModel model = new TokenizerModel(is); This class has three methods tokenize(), tokenizePos() and getTokenProbablities() methods. It can used like this: //Print the tokens for(String tokens: tokenizerME.tokenize(text)){ System.out.println(tokens); } //Get the position of the tokens Span[] spans = tokenizerME.tokenizePos(text); for(Span span: spans){ System.out.println(span); } //Get the token probabilities double[] prob = tokenizerME.getTokenProbabilities(); for(double s: prob){ System.out.println(s); } The output of the above code will be like this:\nHi . How are you ? Hope everything is going well . Welcome to ChatBot Lesson1 . We will try to understand Open Apache NLP for sentence detection [0..2) [2..3) [4..7) [8..11) [12..15) [15..16) [17..21) [22..32) [33..35) [36..41) [42..46) [46..47) [48..55) [56..58) [59..66) [67..74) [74..75) [76..78) [79..83) [84..87) [88..90) [91..101) [102..106) [107..113) [114..117) [118..121) [122..130) [131..140) You can find the code here in the Github repository. References # https://opennlp.apache.org/docs/1.9.2/manual/opennlp.html https://opennlp.apache.org/docs/1.9.2/apidocs/opennlp-tools/index.html https://www.tutorialspoint.com/opennlp/index.htm http://opennlp.sourceforge.net/models-1.5/ This concludes the tokenization using Open ApacheNLP library in Java. This will be another step in using natural language processing.\nAlso, please comment down with your feedback or any suggestions.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/ii-tokenization-using-apache-opennlp/","section":"Posts","summary":"\u003cp\u003eIn this post we will create tokens of the given string using \u003cstrong\u003eApache OpenNLP\u003c/strong\u003e library.\u003c/p\u003e\n\u003cp\u003eThis post is a continuation of the previous post \u003ca href=\"https://www.dynamicallyblunttech.com/post/i-sentence-segmentation-using-apache-opennlp\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cstrong\u003eFind Sentences using Apache Open NLP\u003c/strong\u003e\u003c/a\u003e**. **Refer to this post first for the setting up your project with Apache OpenNLP library. In the previous post we used \u003cem\u003e\u003cstrong\u003eSentenceDetecterME\u003c/strong\u003e\u003c/em\u003e, \u003cem\u003e\u003cstrong\u003eSentenceModel\u003c/strong\u003e\u003c/em\u003e, and ***Span ***class of the openNLP library.\u003c/p\u003e\n\u003cp\u003eApache OpenNLP library provides the \u003cem\u003e\u003cstrong\u003eSimpleTokenizer\u003c/strong\u003e\u003c/em\u003e, ***WhitespaceTokenizer ***and ***TokenizerME ***which will use the normal process to tokenize by splitting the string either on whitespace or using some already trained models. These classes doesn\u0026rsquo;t expose their constructors, so we have to use ***INSTANCE ***to get the instance of these classes.\u003c/p\u003e","title":"II - Tokenization using Apache OpenNLP"},{"content":"In this post we will discuss on recognizing parts of speech in a given sentence using Apache OpenNLP library. This is part of the series on learning Natural Language Processing using Apache OpenNLP library.\nLike other models where we detect sentence, or find names, or tokenize any give string, in this post also we will be using one of the trained models which is provided by Apache Open NLP library only.\nYou can find the other post discussing on detecting sentence, using named entity recognition to find names or tokenize a given string here in these posts Finding Sentences, Finding Names using NamedEntityRecognition and *Tokenization *of the Strings.\nWe will be using en-pos-maxent.bin models which detects the parts of speech and tag it based on the short names. In the below table, you can find the parts of speech and its short form name. Be sure to use the above model, else system will tag it with some other keywords.\nParts of Speech Meaning of parts of speech NN​ Noun, singular or mass ​DT Determiner VB Verb, base form VBD Verb, past tense VBZ Verb, third person singular present IN Preposition or subordinating conjunction NNP Proper noun, singular TO to JJ Adjective As we did while using the other models, here also we will perform the same steps, where we\nLoad the models,\nTokenize the given strings\nTag the tokens with the relevant Parts of Speech short name.\nLets go through the code for each steps:\nLoad the Model #We will use the ***FileInputStream ***to load the relevant models. The model is loaded to create a ***POSModel ***object.\npublic POSModel loadModel(){ try { InputStream is = new FileInputStream(\u0026#34;src\\\\main\\\\resources\\\\models\\\\en-pos-maxent.bin\u0026#34;); POSModel model = new POSModel(is); return model; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } Tokenize Given String #Using the POSModel object created above, we have to create POSTaggerME object and then tokenize the given string.\nPOSModel model = loadModel(); POSTaggerME posObj = new POSTaggerME(model); WhitespaceTokenizer whitespaceTokenizer = WhitespaceTokenizer.INSTANCE; String[] tokens = whitespaceTokenizer.tokenize(sentence); Tagging Tokens #Now the above tokens will be tagged and POSSample object will be created. These POSSample will contain the tokens tagged with the short name of the parts of speech. For an input like this\nString input = \u0026#34;Welcome to DynamicallyBluntTech, for our class of natural language processing.\u0026#34;; We will get an output like this, where each word will tagged with the parts of speech short name.(Given in the above table)\nWelcome_UH to_TO DynamicallyBluntTech,_NNP for_IN our_PRP$ class_NN of_IN natural_JJ language_NN processing._NN We can also monitor the performance using ***PerformanceMonitor ***class.\npublic void performanceMonitor(POSSample sample){ PerformanceMonitor monitor = new PerformanceMonitor(System.err, \u0026#34;uploaded\u0026#34;); monitor.start(); monitor.incrementCounter(); monitor.stopAndPrintFinalResult(); } This will output the below performance metrics:\nAverage: 0.0 uploaded/s Total: 1 uploaded Runtime: 0.0s You can find the entire code for this post as well for others, in my Github Repository link.\nThis concludes the series on Apache OpenNLP library. Also, it provides a command line interface to launch it directly from command prompt/terminal and run the specific commands, but we will be conclude our discussion on OpenNLP library now.\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/iv-recognizing-parts-of-speech-using-apache-opennlp/","section":"Posts","summary":"\u003cp\u003eIn this post we will discuss on recognizing parts of speech in a given sentence using \u003cem\u003e\u003cstrong\u003eApache OpenNLP\u003c/strong\u003e\u003c/em\u003e library. This is part of the series on \u003cem\u003elearning Natural Language Processing\u003c/em\u003e using Apache OpenNLP  library.\u003c/p\u003e\n\u003cp\u003eLike other models where we detect sentence, or find names, or tokenize any give string, in this post also we will be using one of the trained models which is provided by Apache Open NLP library only.\u003c/p\u003e","title":"IV - Recognizing Parts Of Speech Using Apache OpenNLP"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/llm/","section":"Tags","summary":"","title":"LLM"},{"content":" PRs that review themselves. Tests that write themselves. Stand-ups that summarise themselves.\nSay hello to Smart DevOps Assistant (SDA) — an AI-augmented helper that plugs into your developer workflow and reduces review/summary/test friction to near-zero.\nWhy this project exists (and who it’s for) #Modern teams drown in pull requests, context switching, and “Did we test that?” moments. SDA is for engineers who want:\nSignal over noise on PRs — what changed, why it matters, risks, and ownership hints. Safer, faster merges — auto-suggested unit tests raise the quality floor. Continuous team awareness — crisp, Scrum-style updates distilled from commits and PRs. Project-aware AI — responses grounded in your codebase with retrieval-augmented generation (RAG), not generic advice. What SDA does (core capabilities) # PR Analyzer → Summarizes changes, highlights hotspots, suggests improvements. Test Generator → Emits JUnit 5/Mockito test stubs tailored to the changed code. Scrum Assistant → Produces “yesterday / today / blockers” standup notes from PRs \u0026amp; commits. Context-aware via RAG → Indexes your repo/docs into a vector store, fetches the most relevant chunks for each prompt. Architecture at a glance # Backend: Java 24, Spring Boot 3.x, Spring AI for model + embeddings integration. Vector Store: ChromaDB for semantic search over your code \u0026amp; docs (great for quick local iteration). LLM: OpenAI, Hugging Face (pluggable via Spring AI; you can swap providers later). Integrations: GitHub (PR metadata + unified .diff), optional Slack for notifications. Agents: Lightweight services (Code Reviewer, Test Generator, Scrum Assistant) orchestrated by a Spring service; parallelised with @Async + CompletableFuture for speed. High-level flow\nPR opened/updated → Fetch PR title/description/diff → Retrieve relevant code/docs from vector store → Run specialised agents (in parallel) → Aggregate results → Post to Slack / expose via API.\nSDA-Architecture Our build journey (the 5-part roadmap) #Phase 1 — Foundations\nSpring Boot API endpoints: POST /analyze-pr (deep review) POST /generate-summary (short overview) A Java microservice talks to the LLM with clean, role-anchored prompts. Output is structured JSON so the UI/bots can consume it reliably. Phase 2 — Make it visible\nWire GitHub webhook / Action to trigger analysis on PR events. Post summaries/suggestions to Slack. Phase 3 — Make it smart with RAG\nChunk \u0026amp; embed code/docs → store in Chroma via Spring AI. On each PR: retrieve top-K relevant snippets and feed them into prompts alongside the diff. Phase 4 — Multi-agent brains\nAgents for Review, TestGen, Scrum. Parallel execution with CompletableFuture and a simple orchestrator that aggregates outputs. Phase 5 — UX \u0026amp; Ops polish\nSlack/Teams formatting, PR badges, audit logs, and quality-of-life prompts. Tech Stack (and why we chose it) # Spring AI – Gives Spring-native abstractions (Boot starters, config props) to talk to LLMs and embedding models. If you’re a Spring dev, it feels like home. ChromaDB – Simple, fast local vector DB with good ecosystem support, perfect for repo-scale RAG experiments. JUnit 5 + Mockito – Test generation targets familiar Java tooling out of the box. GitHub Webhooks/Actions – Trigger analysis where the devs already live. Alternatives you can explore later: PGVector/MariaDB/Milvus vector stores, or running embeddings locally via Ollama. Spring AI plays nicely with multiple stores/providers.\nPrompting strategy (how we keep it crisp and useful) # Role-anchored prompts: “Senior Reviewer,” “Senior Java Test Engineer,” “Scrum Assistant.” Strict JSON outputs: machine-readable, blog-ready, easy to diff in PR comments. RAG-aware composition: “Given retrieved context + this diff, do X.” Guardrails: explicit frameworks (JUnit/Mockito), concise tone, edge-case nudges. How SDA works #SDA depends on collections of microservices which interact with each other to provide summary or generate test case. All services are registered via Eureka (Service Discovery) and each request goes through Spring cloud Gateway (API Gateway).\nThere is a separate service for handling all operation with Github, e.g.\nFinding PR metadata Finding the PR difference Fetching all changed files in a PR Fetching any file content in the project Share data with AI service And then there is a service to interact with AI Agents. Based on the data it gets from the Git service, it will try to\nSummary of the PR Analyses PR for any risk, suggestions and improvements APIs to manage data in ChromaDB Vector store. Have a counterpart with multiple agents like Code Reviewer Agent - which provide meaningful context based suggestion on the PR Test Generator Agent - which generates the Junit test cases based on the PR and project as the context. Scrum Assistant Agent - which will use the PR to provide SCRUM update on what was done yesterday, what is planned for today and tomorrow. Dividing the project in phases facilitate in bringing the project from bottom to top, with ground work done in phase 1, and then integrating with AI agents and providing essential time for the project to evolve. Faced a lot of issues and sometimes need to backtrack to enhance the requirement.\nThis project is for learning purpose. Some parts may have some issues, which is getting fixed as you read through.\nAs part of Phase 1, the project has all the services interweaved in one single microservice. The Git Webhook is used to trigger a request to the application.\nSo, if a PR is raised on the project, then the request from Git Webhook is redirected to the application, then SDA will picks up the required metadata from the Github and then it initialise the Vector store with the changed files coming from the PR. then it collects the PR difference and sends the data to AI agents with prompt to get suggestions/improvements on PR. NGROK - Flexible API Gateway #Since the application is not hosted on any domain, rather sits in my localhost, we have to somehow redirect the request coming from Github Webhook to our application. Or it can be said that, the Github Webhook needs a URL or host to send the request.\nThis is where the https://ngrok.com/ application comes in to play. It launches its server which can accept the API request from Github Webhook, and redirects it to our application running on localhost.\nSo, lets say my application is running on port 8389, then we need to run\nngrok http http://localhost:8389 which will launch a server and provide URL that can be used with Github webhook. NGROK The URL as part of Forwarding can be used now in Github Webhook.\nIn the Github, under the settings tab, go to Webhooks, click on Add Webhook, and fill out the details like Payload URL (in this case https://aab4835964fe.ngrok-free.app) and Content Type (application/json). You can also enable SSL verification for more secure routing. Enable SSL Once enabled, it will send the POST request with the payload to SDA.\nAPI /analyzepr #SDA has and endpoint which will accept the POST request from the Github Webhook, and send the payload to\nfetch the PR difference fetch the changed files name in the PR filter for Java files and skip the removed files in the PR fetch the entire file content and store in ChromaDb Vector Store The file content will act as the context for the AI agent to analyse the PR and provide suggestions/improvements. GitService.analyzePR() Then the SDA prepares the prompt with the PR difference and injects the QuestionAnswerAdvisor of Spring AI to prevents model from generating a general PR summary, instead a more context aware response related to the files changed in the PR. And AI will generate a structure response like AI Response Gotchas we hit (so you don’t) # Spring AI dependency names \u0026amp; properties evolve. Prefer the latest docs and BOM; older guides might use milestone artifacts or different property names. Pin compatible versions and read the starter docs when integrating Chroma. Vector store/API drift. Ensure your Chroma server and client expectations match; upgrade pairs together. Agents ≠ framework magic. Spring AI doesn’t ship a multi-agent runtime; we implemented a lean orchestrator and parallelised with @Async + CompletableFuture. What’s next in the series # Part 2: Scaffolding the APIs \u0026amp; clean prompts (with sample payloads). Part 3: RAG in Java — chunking, embedding, retrieval with Spring AI + Chroma. Part 4: Multi-agent coordination + parallel execution patterns. Part 5: CI hooks, Slack summaries, and real-world ops tips. Try it / follow along #Clone the repo, run the services, and start with POST /generate-summary on any PR (or paste a unified .diff). Then layer in RAG and multi-agent outputs as we progress through the series.\nYou can refer to the Github project here\nA final word #SDA isn’t about replacing reviewers; it’s about giving them superpowers. Less time expanding diffs, more time debating design, correctness, and the “gotchas” that matter.\nDo comment for any suggestions or topics you want to be addressed. Follow for more tech related content.\nLearn with Every Commit\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/meet-smart-devops-assistant-vision-stack-and-journey-so-far/","section":"Posts","summary":"\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003ePRs that review themselves. Tests that write themselves. Stand-ups that summarise themselves.\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eSay hello to \u003cstrong\u003eSmart DevOps Assistant (SDA)\u003c/strong\u003e — an AI-augmented helper that plugs into your developer workflow and reduces review/summary/test friction to near-zero.\u003c/p\u003e\n\u003ch2 id=\"why-this-project-exists-and-who-its-for\" class=\"relative group\"\u003e\u003cstrong\u003eWhy this project exists (and who it’s for)\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#why-this-project-exists-and-who-its-for\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eModern teams drown in pull requests, context switching, and “Did we test that?” moments. SDA is for engineers who want:\u003c/p\u003e","title":"Meet Smart Devops Assistant: Vision, Stack and Journey so far"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/vaccine/","section":"Tags","summary":"","title":"Vaccine"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/6249579158f0bd10b2a57273/","section":"Categories","summary":"","title":"\"6249579158f0bd10b2a57273\"]"},{"content":"As we discussed in the previous post on Domain 1-Agentic Architecture and Orchestration, this part of the post will cover the Domain 2 which holds 18% weightage in the exam. #If you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. #Where to Register: https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\nUnofficial practice set, written to match publicly reported blueprint topics — not sourced from the real exam bank. Use for concept drilling, not as a guarantee of exam content.\nTopics Covered # Tool Design Principles (descriptions, boundaries) Input Formats, Examples \u0026amp; Edge Cases Structured Error Handling Tool Distribution Across Agents Tool Choice Configuration (tool_choice) Tool Selection / Routing Behavior Model Context Protocol (MCP) — Core Concepts MCP Servers, Clients, and Resources MCP Transport (stdio vs SSE/HTTP) — Trade-offs Authentication Patterns for MCP Built-in Claude Tools Scoped Permissions \u0026amp; Safe Tool Usage Tool Schemas (writing reliable ones) Integrating MCP into Claude Code / Agent workflows 1. Tool Design Principles — Descriptions \u0026amp; Boundaries #A tool description is the plain-English explanation you give Claude about what a tool does, when to use it, and what it returns.\nBoundaries mean making clear what the tool is for and not for, so Claude doesn\u0026rsquo;t reach for it in the wrong situation. Vague or overlapping descriptions cause Claude to pick the wrong tool.\nKey rule: the description should read like documentation for a new team member — purpose, when to use it, and when NOT to use it.\nQ1. Two tools exist: search_orders and lookup_order, with near-identical descriptions. What problem does this most likely cause?\nA) Faster tool selection\nB) Claude may call the wrong one, or waste a call trying both\nC) No effect — Claude always picks correctly\nD) It reduces token usage\nAnswer: B — overlapping/unclear boundaries between similarly-named tools cause tool-selection errors.\nQ2. Which tool description is best?\nA) \u0026ldquo;get_data — gets data\u0026rdquo;\nB) \u0026ldquo;invoice_tool — does invoice stuff, also sometimes orders\u0026rdquo;\nC) \u0026ldquo;get_customer_invoice — retrieves a single invoice by invoice ID; use only for invoice lookups, not for order or shipping data\u0026rdquo;\nD) No description, rely on the function name\nAnswer: C — specific purpose, specific scope, explicit exclusion.\nQ3. True/False: A tool\u0026rsquo;s name alone (without a description) is usually sufficient for reliable tool selection.\nAnswer: False — names are ambiguous at scale; descriptions carry the real signal.\n2. Input Formats, Examples \u0026amp; Edge Cases #Specifying the exact shape of input a tool expects (data types, required vs optional fields, formats like date strings), plus example calls and known edge cases (empty results, invalid IDs) so Claude knows how to call the tool correctly and what to expect back.\nQ4. A get_weather tool expects a date as YYYY-MM-DD but the schema just says date: string. What\u0026rsquo;s the risk?\nA) None, Claude always guesses correctly\nB) The tool will auto-correct any format\nC) Claude may pass an ambiguous format (e.g. \u0026ldquo;tomorrow\u0026rdquo; or \u0026ldquo;03/04/2026\u0026rdquo;), causing tool errors\nD) This only matters for numeric fields\nAnswer: B — always specify exact format expectations, ideally with an example in the description.\nQ5. Why include example queries/example calls in a tool\u0026rsquo;s description?\nA) They\u0026rsquo;re required by the MCP spec\nB) They anchor Claude\u0026rsquo;s understanding of correct usage, especially for less-common parameter combinations\nC) They replace the need for a schema\nD) They reduce the tool\u0026rsquo;s latency\nAnswer: B.\nQ6. A search_products tool has no defined behavior for \u0026ldquo;zero results found.\u0026rdquo; What should the tool design account for?\nA) Nothing — Claude will assume success\nB) The tool should throw a fatal error\nC) Zero-result cases can\u0026rsquo;t happen\nD) An explicit edge case: define what\u0026rsquo;s returned (e.g., empty array + message) so Claude doesn\u0026rsquo;t misinterpret it as an error or hallucinate results\nAnswer: D.\n3. Structured Error Handling #Instead of returning a raw crash or a vague failure, a well-designed tool returns structured, informative error responses (e.g., {\u0026ldquo;error\u0026rdquo;: \u0026ldquo;invalid_order_id\u0026rdquo;, \u0026ldquo;message\u0026rdquo;: \u0026ldquo;\u0026hellip;\u0026rdquo;}) so Claude can understand what went wrong and decide what to do next (retry, ask user, try a different tool).\nQ7. A tool fails and returns just \u0026ldquo;Error\u0026rdquo; with no detail. What\u0026rsquo;s the consequence for the agent loop?\nA) Claude will always retry correctly\nB) No consequence, the loop still terminates properly\nC) Claude lacks enough information to decide the right recovery action (retry vs. abort vs. ask for clarification)\nD) This is the correct way to signal failure\nAnswer: C.\nQ8. Which is a well-structured tool error response?\nA) \u0026ldquo;failed\u0026rdquo;\nB) {\u0026ldquo;status\u0026rdquo;: \u0026ldquo;error\u0026rdquo;, \u0026ldquo;error_code\u0026rdquo;: \u0026ldquo;insufficient_funds\u0026rdquo;, \u0026ldquo;message\u0026rdquo;: \u0026ldquo;Account balance is $12, requested $50\u0026rdquo;, \u0026ldquo;retryable\u0026rdquo;: false}\nC) An HTTP 500 with no body\nD) Silently returning an empty string\nAnswer: B — structured, specific, and tells Claude whether retrying makes sense.\nQ9. Why should error responses indicate whether a failure is retryable?\nA) It\u0026rsquo;s purely cosmetic\nB) MCP requires this field\nC) It has no effect on agent behavior\nD) It lets Claude (or the orchestrating code) decide whether to retry automatically or escalate/stop\nAnswer: D.\n4. Tool Distribution Across Agents #Deciding which tools go to which agent/subagent in a multi-agent system. Related to \u0026ldquo;scoped tool access\u0026rdquo; from Domain 1, but here it\u0026rsquo;s specifically about designing the tool set per role.\nQ10. In a hub-and-spoke system, a \u0026ldquo;billing subagent\u0026rdquo; and a \u0026ldquo;shipping subagent\u0026rdquo; both get the exact same full toolset of 20 tools. What\u0026rsquo;s the design flaw?\nA) None — more tools is always better\nB) Tools should be distributed by role; giving both agents the same broad set increases confusion and misuse risk instead of scoping tools to each agent\u0026rsquo;s actual job\nC) This is required by MCP\nD) Subagents cannot have different toolsets\nAnswer: B.\nQ11. What\u0026rsquo;s a good principle for distributing tools across a multi-agent system?\nA) Every agent gets every tool \u0026ldquo;just in case\u0026rdquo;\nB) Each agent gets the minimal set of tools required for its specific responsibility\nC) Only the coordinator should have any tools\nD) Tool distribution doesn\u0026rsquo;t affect reliability\nAnswer: B.\n5. Tool Choice Configuration (tool_choice) #An API-level setting controlling how Claude is allowed to use tools in a given call:\nauto — Claude decides whether to call a tool or just respond in text any — Claude must call some tool, but picks which one tool (specific) — Claude is forced to call one named tool none — Claude must not call any tool Q12. You want to force Claude to always extract data using exactly one specific tool, never respond in free text. Which tool_choice setting fits?\nA) auto\nB) any\nC) A specific named tool (tool choice)\nD) none\nAnswer: C.\nQ13. In an open-ended research agent where Claude should decide for itself whether a tool is needed at each step, which setting is correct?\nA) auto\nB) none\nC) A specific forced tool\nD) any\nAnswer: A.\nQ14. When would tool_choice: none make sense?\nA) When you want Claude to guarantee a tool call\nB) When you want a plain text-only response for this turn, with no tool use, even though tools are defined\nC) When no tools exist at all\nD) Never — this option doesn\u0026rsquo;t exist\nAnswer: B.\n6. Tool Selection / Routing Behavior #How Claude decides which tool (of many available) to call for a given task — driven by description quality, boundaries, and how distinct the tools are from each other. \u0026ldquo;Routing\u0026rdquo; here means the agent choosing the correct path/tool based on the input.\nQ15. A customer query \u0026ldquo;Where\u0026rsquo;s my order?\u0026rdquo; could match both track_shipment and get_order_status. What causes ambiguous routing here?\nA) Descriptions/boundaries that don\u0026rsquo;t clearly differentiate the two tools\u0026rsquo; purposes\nB) Claude cannot handle 2 similar tools\nC) MCP protocol limitation\nD) The query is too long\nAnswer: A.\nQ16. What\u0026rsquo;s the best fix for chronically incorrect tool routing between two similar tools?\nA) Delete both tools\nB) Increase temperature\nC) Sharpen the descriptions to clarify distinct purpose/scope, or merge them if they\u0026rsquo;re truly redundant\nD) Add more unrelated tools to dilute the choice\nAnswer: C.\n7. Model Context Protocol (MCP) — Core Concepts #MCP is an open standard/protocol that lets AI applications connect to external tools, data sources, and systems in a consistent way. Three core roles:\nHost — the application the user interacts with (e.g., Claude Code, Claude Desktop) Client — lives inside the host, manages one connection to one MCP server Server — exposes tools, resources, and prompts to the client Q17. What is the relationship between an MCP host and an MCP client?\nA) They are the same thing\nB) The host application contains one or more clients, each client maintaining a 1:1 connection to a server\nC) The client is external to the host\nD) A host can only ever have one client\nAnswer: B.\nQ18. What does an MCP server expose to a client?\nA) Only raw text\nB) Tools, resources, and prompts\nC) Only the model weights\nD) Nothing — servers are passive\nAnswer: B.\nQ19. Why does MCP exist as a standard, rather than every integration being custom-built?\nA) To make integrations harder\nB) To give a consistent, reusable way for any AI app to connect to any tool/data source, instead of N×M custom integrations\nC) It\u0026rsquo;s only for Anthropic\u0026rsquo;s internal use\nD) It replaces the need for tool schemas\nAnswer: B.\n8. MCP Servers, Clients \u0026amp; Resources #Beyond tools, MCP servers can expose resources (read-only data the client can fetch, like files or records) and prompts (reusable prompt templates the server provides). A server is typically a discrete process/service tied to one system (e.g., a GitHub MCP server, a Postgres MCP server).\nQ20. What\u0026rsquo;s the difference between an MCP tool and an MCP resource?\nA) No difference\nB) A tool is an action Claude can invoke (with side effects or computation); a resource is more like readable data/context the client can pull in\nC) Resources can only be used by the server, never the client\nD) Tools are read-only, resources are write-only\nAnswer: B.\nQ21. A team wants to connect Claude to both a GitHub repo and a company Postgres database. What\u0026rsquo;s the correct MCP architecture?\nA) One server handling both, undifferentiated\nB) MCP doesn\u0026rsquo;t support databases\nC) A single client with no server\nD) Separate MCP servers, one per system, each exposing tools/resources scoped to that system\nAnswer: D — MCP servers are typically scoped to one system/integration.\n9. MCP Transport — stdio vs SSE/HTTP Trade-offs #Transport is how the client and server communicate. Common options:\nstdio — local process communication (client launches server as a subprocess); simple, fast, good for local tools, but tied to one machine. SSE / HTTP (remote/streamable HTTP) — server runs remotely, communicates over network; enables remote/shared servers, multiple clients, but adds network latency, auth complexity, and connection management concerns. Q22. You\u0026rsquo;re building an MCP server that must be shared across many users\u0026rsquo; Claude Code instances from a central deployed service. Which transport fits better?\nA) stdio\nB) SSE/HTTP (remote transport)\nC) Neither — MCP doesn\u0026rsquo;t support remote servers\nD) It doesn\u0026rsquo;t matter\nAnswer: B — remote/shared access needs a network transport, not a local subprocess.\nQ23. What\u0026rsquo;s a trade-off of choosing stdio transport for an MCP server?\nA) It requires complex authentication\nB) It\u0026rsquo;s local-only — tied to the machine running the client, not shareable across users/services\nC) It\u0026rsquo;s always slower than SSE\nD) It can\u0026rsquo;t expose tools\nAnswer: B.\nQ24. What\u0026rsquo;s a key operational concern introduced specifically by SSE/HTTP transport that stdio doesn\u0026rsquo;t have?\nA) Tool schema design\nB) Authentication, network reliability, and connection/session management over the wire\nC) The need for tool descriptions\nD) Structured error handling\nAnswer: B.\n10. Authentication Patterns for MCP #How an MCP client proves its identity / gets authorized access to a server, especially for remote servers. Patterns include API keys, OAuth flows, and scoped tokens — chosen based on whether the server is local/trusted or remote/multi-tenant.\nQ25. A remote MCP server exposes access to a company\u0026rsquo;s internal ticketing system for multiple external users. What\u0026rsquo;s the most appropriate authentication approach?\nA) No authentication needed since it\u0026rsquo;s \u0026ldquo;just a tool\u0026rdquo;\nB) A proper auth flow (e.g., OAuth or scoped tokens) so each user only accesses data they\u0026rsquo;re authorized for\nC) A single shared static API key hardcoded into every client\nD) Authentication is handled automatically by MCP with no configuration\nAnswer: B.\nQ26. Why is a single shared credential across all users of a remote MCP server considered a poor pattern?\nA) It\u0026rsquo;s more efficient\nB) MCP forbids shared credentials technically\nC) It removes per-user access boundaries — one compromised client exposes everyone\u0026rsquo;s access, and you can\u0026rsquo;t scope permissions per user\nD) It\u0026rsquo;s actually the recommended pattern\nAnswer: C.\n11. Built-in Claude Tools #Tools Claude/Claude Code ships with out of the box, without needing an MCP server — e.g., file tools (Read, Edit, Write, Glob, Grep), and execution tools (Bash). These handle common local dev tasks; MCP is for external/custom integrations beyond these.\nQ27. When should you build a custom MCP tool instead of relying on Claude Code\u0026rsquo;s built-in file tools?\nA) Never — built-ins cover every case\nB) Built-in tools should always be replaced by MCP\nC) MCP tools are only for file operations too\nD) When the task needs access to something outside the local filesystem/shell — e.g., an external API, database, or SaaS product\nAnswer: D.\nQ28. What is Grep (as a built-in tool) primarily used for?\nA) Editing files\nB) Searching file contents for patterns\nC) Making network requests\nD) Running shell scripts\nAnswer: B.\nQ29. User is requesting to look for all *.md files which are present in folder to read the architecture prepared by Claude. Which tool will be used for file name search ?\nA) glob\nB) grep\nC) bash\nD) wildcard search not possible\nAnswer: A - Glob finds files by name pattern — you give it something like .java or **/Controller.java and it returns a list of matching file paths. It doesn\u0026rsquo;t look inside the files at all.\n12. Scoped Permissions \u0026amp; Safe Tool Usage #Restricting what a tool/agent is allowed to do — e.g., read-only vs. write access, which directories/records it can touch, or requiring approval before high-risk actions run. This is the practical safety layer around tool use.\nQ30. A code-review agent should be able to read any file in a repo but should NEVER be allowed to push to the remote or delete branches. How should this be enforced?\nA) A prompt instruction asking it nicely not to\nB) Trust the model\u0026rsquo;s judgment alone\nC) Scoped permissions / hooks that technically restrict which tools or actions are available, not just prompt-level requests\nD) Give it all tools and monitor after the fact only\nAnswer: C — consistent with Domain 1\u0026rsquo;s hooks-vs-prompts distinction: hard restrictions need enforcement, not persuasion.\nQ31. What does \u0026ldquo;safe tool usage\u0026rdquo; mean in the context of an agent with filesystem access?\nA) Giving it unrestricted access for convenience\nB) Disabling all tools\nC) Only using tools that have no side effects, ever\nD) Scoping access to only the necessary directories/files and requiring confirmation for destructive actions\nAnswer: D.\n13. Tool Schemas (Writing Reliable Ones) #The formal, structured definition of a tool\u0026rsquo;s inputs (JSON Schema-style: types, required fields, enums, descriptions per field) that Claude uses to know exactly how to call it. Good schemas reduce malformed calls and validation failures.\nQ32. A schema defines status as a free-text string, but only 4 valid values actually exist (pending, shipped, delivered, cancelled). What\u0026rsquo;s the fix?\nA) Leave it as a free string\nB) Remove the field entirely\nC) Use an enum constraining status to those 4 exact values, so Claude can\u0026rsquo;t pass an invalid one\nD) Make it a boolean\nAnswer: C.\nQ33. Why mark fields as required vs optional explicitly in a tool schema?\nA) It has no functional effect\nB) It tells Claude precisely which inputs must always be provided vs. which can be omitted, reducing malformed calls\nC) All fields should always be optional\nD) MCP ignores the required field\nAnswer: B.\nQ34. A tool schema has 25 required parameters for a single call. What design problem does this likely cause?\nA) High cognitive load / error rate — Claude is more likely to omit or mis-fill parameters; consider breaking into multiple simpler tools or reducing required fields\nB) None, more parameters means more precision\nC) MCP has no parameter limit so it\u0026rsquo;s fine\nD) It reduces the chance of tool-selection errors\nAnswer: A.\n14. Integrating MCP into Claude Code / Agent Workflows #Connecting MCP servers into an actual Claude Code project or agent system — configuring which servers are available, at what scope (project vs. user level), and how their tools combine with built-in tools during real workflows.\nQ35. A company wants an MCP server (e.g., Jira integration) available to everyone on a project, checked into version control, vs. a developer\u0026rsquo;s personal Slack MCP server only for themselves. What\u0026rsquo;s the right configuration split?\nA) Both should be user-level only\nB) Project-level config (e.g. .mcp.json checked into the repo) for the shared Jira server; user/personal-level config for the individual\u0026rsquo;s Slack server\nC) Both should be project-level\nD) MCP doesn\u0026rsquo;t support per-project configuration\nAnswer: B — matches the project-level vs. user-level MCP config split.\nQ36. Why scope some MCP servers at the project level rather than always at the user level?\nA) Project-level is always slower\nB) So the whole team automatically gets consistent, shared tool access tied to that specific project/repo, without each person configuring it manually\nC) User-level configs are deprecated\nD) It has no practical benefit\nAnswer: B.\nSCENARIO QUESTIONS (mixed topics, exam-realistic) #Q37. An MCP server for a payments system lets any authenticated client call issue_refund with no scoping. A support agent using this server can now refund any amount to any account. What are the TWO design flaws?\nA) Bad tool naming; missing examples\nB) Missing scoped permissions (e.g., refund limits/approval) and inadequate authentication/authorization boundaries\nC) Wrong transport choice\nD) Missing enum in schema\nAnswer: B.\nQ38. A tool process_payment sometimes fails silently and returns null. Claude interprets this as success and tells the user their payment went through when it didn\u0026rsquo;t. What domain-2 principle was violated?\nA) Structured error handling — failures must be clearly signaled, not returned as ambiguous/null success-like responses\nB) Tool distribution\nC) Transport selection\nD) Tool choice configuration\nAnswer: A.\nQ39. A team builds 15 separate near-duplicate tools (get_user_v1, get_user_v2, fetch_user, user_lookup\u0026hellip;) accumulated over time. What Domain 1 anti-pattern does this create, and what Domain 2 fix addresses it?\nA) Anti-pattern: same-session self-review. Fix: hooks\nB) Anti-pattern: tool sprawl. Fix: consolidate/redesign tool boundaries and descriptions so each tool has one clear, non-overlapping purpose\nC) Anti-pattern: natural-language termination. Fix: stop_reason\nD) No anti-pattern present\nAnswer: B — ties Domain 1\u0026rsquo;s tool sprawl anti-pattern directly to Domain 2\u0026rsquo;s tool design fix.\nQ40. You\u0026rsquo;re designing an MCP server for a healthcare records system used by multiple hospitals remotely. Which combination of choices is most appropriate?\nA) stdio transport, no authentication, single shared tool with 30 parameters\nB) Remote (SSE/HTTP) transport, per-user OAuth-based authentication, scoped permissions per hospital/role, and well-defined schemas with enums for constrained fields\nC) stdio transport, shared static API key, unscoped access\nD) No MCP server needed, use built-in tools only\nAnswer: B — remote + multi-tenant + sensitive data demands network transport, real auth, and scoping.\nQ41. A search_flights tool\u0026rsquo;s description says only \u0026ldquo;searches flights.\u0026rdquo; Claude keeps calling it with malformed date ranges and gets confused about round-trip vs one-way. What are the missing elements?\nA) MCP transport configuration\nB) Authentication pattern\nC) Tool distribution across agents\nD) Clear input format specification, examples, and edge-case handling in the schema/description\nAnswer: D.\nQUICK REFERENCE TABLE # Concept One-line definition Tool description Plain-English explanation of what a tool does and when to use it Tool boundaries What a tool is/isn\u0026rsquo;t for, to avoid overlap with other tools Structured error Machine-readable failure response with code/message/retryable flag Tool distribution Deciding which tools go to which agent/subagent tool_choice API setting: auto / any / specific tool / none MCP Host The application the user uses (e.g. Claude Code) MCP Client Lives in host; 1:1 connection to a server MCP Server Exposes tools/resources/prompts to a client Resource (MCP) Read-only data exposed by a server stdio transport Local subprocess communication; simple, single-machine SSE/HTTP transport Remote/networked; supports shared, multi-client access Scoped permissions Restricting what actions/data a tool or agent can touch Tool schema Structured input definition (types, required fields, enums) Project-level MCP config Shared team config (e.g. .mcp.json in repo) User-level MCP config Personal config, not shared with the team Referring to these questions just gives you an idea how the exam looks like, and can vary. Share if you have any answers or questions not correctly placed or explained.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/cca-f-domain-2-tool-design-and-mcp-integration-key-concepts/","section":"Posts","summary":"\u003ch3 id=\"as-we-discussed-in-the-previous-post-on-domain-1-agentic-architecture-and-orchestration-this-part-of-the-post-will-cover-the-domain-2-which-holds-18-weightage-in-the-exam\" class=\"relative group\"\u003eAs we discussed in the previous post on \u003ca href=\"/post/cca-f-domain1-agentic-architecture-orchestration-key-concepts\"\u003eDomain 1-Agentic Architecture and Orchestration\u003c/a\u003e, this part of the post will cover the Domain 2 which holds 18% weightage in the exam. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#as-we-discussed-in-the-previous-post-on-domain-1-agentic-architecture-and-orchestration-this-part-of-the-post-will-cover-the-domain-2-which-holds-18-weightage-in-the-exam\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003ch3 id=\"if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" class=\"relative group\"\u003eIf you have directly stumbled upon this page, I am preparing for the Claude Certified Architect — Foundations (CCA-F), and placing all types of practice questions that I encounter. This may help others, but objective is to keep track of key concept and some practice question that define the topics as well. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#if-you-have-directly-stumbled-upon-this-page-i-am-preparing-for-the-claude-certified-architect--foundations-cca-f-and-placing-all-types-of-practice-questions-that-i-encounter-this-may-help-others-but-objective-is-to-keep-track-of-key-concept-and-some-practice-question-that-define-the-topics-as-well\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eWhere to Register: \u003ca href=\"https://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://anthropic-partners.skilljar.com/claude-certified-architect-foundations-certification\u003c/a\u003e\u003c/p\u003e","title":"CCA-F Domain 2: Tool Design and MCP Integration - Key Concepts"},{"content":"In this post, we will create a Kafka producer which will produce string messages as Rest call and a consumer which will consume the produced messages.\nInstall/Setup Kafka #To download Kafka refer to the Apache Kafka Download website.\nPick any of the desired package. For this post we chose 2.8.1\nScala 2.12 - kafka_2.12-3.0.0.tgz (asc, sha512) NOTE: The version 2.13.3 is not working and will throw Connection Refused error. #Steps to Setup: #Extract the tgz file to some directory.\nOpen zookeeper.properties file under config directory and provide the ***dataDir ***value. It is the data directory used by Kafka to store the data.\nOpen server.properties file under config directory and provide the logs-dir kafka logs directory.\nStart the zookeeper by running following command:\nif Windows: .\\bin\\windows\\zookeeper-server-start.bat .\\config\\zookeeper.properties if Unix: .\\bin\\zookeeper-server-start.sh .\\config\\zookeeper.properties NOTE: **Problem: **If you are getting following error:\n\u0026quot;***input line is too long kafka*** ***The syntax is not correct\u0026quot;*** Reason: This is happening because the allowed characters in windows for classpath is only 8191 characters. But when the above command is executed, it sets each jar file found under the lib directory to the classpath variable. This make the statement increase the characters length threshold.\nSolution:\nOpen the kafka-run-class.bat file under bin/windows directory.\nSearch for \u0026ldquo;rem Classpath addition for release*\u0026rdquo; *\n***Replace these line** *rem Classpath addition for release* for %%i in (\u0026quot;%BASE_DIR%\\libs\\*\u0026quot;) do (* call :concat \u0026quot;%%i\u0026quot;* )* ***With these** *rem Classpath addition for release* *call :concat \u0026quot;%BASE_DIR%\\libs\\*;\u0026quot;* Once the zookeeper is started successfully without any error, then start kafka server using below command:\nFor Windows:\n*.\\bin\\windows\\kafka-server-start.bat .\\config\\server.properties* For Unix: .\\bin\\kafka-server-start.sh .\\config\\server.properties\nIf you want to stop, then run the corresponding stop scripts for zookeeper and kafka server.\nCreate Kafka Topic #When a message is sent to Kafka broker, it will send the message to a specific topic, which can be thought of as address where the post can be delivered. This way consumer can differentiate between the messages which are relevant to get consumed than all other messages.\nWe can create a kafka topic like this:\n**For Unix: **\n./kafka-topics.sh --create --topic mt -zookeeper localhost:2181 --replication-factor 1 --partitions 1 For Windows\n./windows/kafka-topics.bat --create --topic mt -zookeeper localhost:2181 --replication-factor 1 --partitions 1 Create Spring Project #From the Spring Initializer, either from IDE or the website, create a spring maven project with the following dependency:\nSpring Web (Maven Repo) Kafka - Spring (Maven Repo) Creating Producer Based on Configurations #The project will have a producer which will produce the string messages.\n@Configuration public class ProducerConfigs { private static final String KAFKA_BROKER = \u0026#34;localhost:9092\u0026#34;; @Bean public KafkaTemplate\u0026lt;String, String\u0026gt; kafkaTemplate(){ return new KafkaTemplate\u0026lt;\u0026gt;(producerFactory()); } @Bean public ProducerFactory\u0026lt;String, String\u0026gt; producerFactory(){ return new DefaultKafkaProducerFactory\u0026lt;\u0026gt;(producerConfigurations()); } @Bean public Map\u0026lt;String, Object\u0026gt; producerConfigurations(){ Map\u0026lt;String, Object\u0026gt; config = new HashMap\u0026lt;\u0026gt;(); config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_BROKER); config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); return config; } } In the above code base, we are defining the producer configuration which states that the producer will be producing the messages to the ***KAFKA_BROKER *** URL defined, and the Key and Value of the message will be of type String.\nHere the Kafka template is used to produce messages from producer created from the producer factory . The producer are created based on configurations given.\nCreating Consumer Based on Configurations #The project will have a consumer which will consumer the string messages.\nprivate static final String KAFKA_BROKER = \u0026#34;localhost:9092\u0026#34;; private static final String GROUP_ID = \u0026#34;kafka-sandbox\u0026#34;; @Bean public ConcurrentKafkaListenerContainerFactory\u0026lt;String, String\u0026gt; kafkaListenerContainerFactory(){ ConcurrentKafkaListenerContainerFactory\u0026lt;String, String\u0026gt; factory = new ConcurrentKafkaListenerContainerFactory\u0026lt;\u0026gt;(); factory.setConsumerFactory(consumerFactory()); return factory; } @Bean public ConsumerFactory\u0026lt;String, String\u0026gt; consumerFactory(){ return new DefaultKafkaConsumerFactory\u0026lt;\u0026gt;(consumerConfigurations()); } @Bean public Map\u0026lt;String, Object\u0026gt; consumerConfigurations(){ Map\u0026lt;String, Object\u0026gt; config = new HashMap\u0026lt;\u0026gt;(); config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_BROKER); config.put(ConsumerConfig.GROUP_ID_CONFIG, GROUP_ID); config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); return config; } In the above code base, with the ***KAFKA_BROKER ***it will also require the ***GROUP_ID ***which is mandatory by Kafka for parallel data consumptions. Similarly the ***ConcurrentKafkaListenerContainerFactory ***is used to consume multiple messages by different threads.\nHere the consumer configurations is given to connect to the ***KAFKA_BROKER ***with the ***GROUP_ID ***and the Key and Value deserialize class (here it is String).\nCreate Topic-wise Consumer for specific action #This will be topic specific consumer where you can perform specific actions. Here we will listen to the kafka for any new messages produced on the kafka topic we created and will store it to a list. We will also create a getMessages() method to get the list of messages.\nprivate final List\u0026lt;String\u0026gt; messages = new ArrayList\u0026lt;\u0026gt;(); @KafkaListener(topics = \u0026#34;mt\u0026#34;, groupId = \u0026#34;kafka-sandbox\u0026#34;) public void listen(String message){ synchronized (message){ messages.add(message); } } public List\u0026lt;String\u0026gt; getMessages(){ return messages; } It listens on the topics mentioned and the group-id given.\nCreate Rest Controller to produce and consume messages #In the Rest controller, we will create a produce method which will send the data via kafka template on the given topic.\n@GetMapping(\u0026#34;/kafka/produce\u0026#34;) public void produce(@RequestParam String message){ kafkaTemplate.send(\u0026#34;mt\u0026#34;, message); } And will create a another method which will display all the messages produced by the producer.\n@GetMapping(\u0026#34;/kafka/messages\u0026#34;) public List\u0026lt;String\u0026gt; getMessages(){ return mtConsumer.getMessages(); } Start the spring application and run the following get REST call to send any specific messages like this where we are sending 4 messages to kafka topic mt:\nhttp://localhost:8080/kafka/produce?message=\u0026ldquo;Kafka with Java on DynamicallyBluntTech1\u0026rdquo;\nhttp://localhost:8080/kafka/produce?message=\u0026ldquo;Kafka with Java on DynamicallyBluntTech2\u0026rdquo;\nhttp://localhost:8080/kafka/produce?message=\u0026ldquo;Kafka with Java on DynamicallyBluntTech3\u0026rdquo;\nhttp://localhost:8080/kafka/produce?message=\u0026ldquo;Kafka with Java on DynamicallyBluntTech4\u0026rdquo;\nSimilarly, we can run the REST call for getMessages() to display all the messages which the consumer has consumed, like below:\nhttp://localhost:8080/kafka/messages\nwhich will display following 4 messages which was produced:\n[\u0026quot;\u0026quot;Kafka with Java on DynamicallyBluntTech1\u0026quot;\u0026quot;, \u0026ldquo;\u0026quot;Kafka with Java on DynamicallyBluntTech2\u0026quot;\u0026rdquo;, \u0026ldquo;\u0026quot;Kafka with Java on DynamicallyBluntTech3\u0026quot;\u0026rdquo;, \u0026ldquo;\u0026quot;Kafka with Java on DynamicallyBluntTech4\u0026quot;\u0026rdquo;]\nIn this way we can produce and consumer string messages. We can also produce and consume the JSON object as well, only change would be to use ***Jsonserializer ***and ***JsonDeserializer ***class as part of the configuration given to the producer and consumer. To get the entire code base of the above example, refer to this Github link here.\nReferences #https://developer.okta.com/blog/2019/11/19/java-kafka https://www.geeksforgeeks.org/spring-boot-how-to-publish-string-messages-on-apache-kafka/\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/produce-consume-string-messages-using-apache-kafka-and-java/","section":"Posts","summary":"\u003cp\u003eIn this post, we will create a Kafka producer which will produce string messages as Rest call and a consumer which will consume the produced messages.\u003c/p\u003e\n\u003ch2 id=\"installsetup-kafka\" class=\"relative group\"\u003e\u003cstrong\u003eInstall/Setup Kafka\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#installsetup-kafka\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eTo download Kafka refer to the \u003ca href=\"https://kafka.apache.org/downloads\" target=\"_blank\" rel=\"noreferrer\"\u003eApache Kafka Download website\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003ePick any of the desired package. For this post we chose 2.8.1\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eScala 2.12  - \u003ca href=\"https://www.apache.org/dyn/closer.cgi?path=/kafka/3.0.0/kafka_2.12-3.0.0.tgz\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003ekafka_2.12-3.0.0.tgz\u003c/em\u003e\u003c/a\u003e (\u003ca href=\"https://downloads.apache.org/kafka/3.0.0/kafka_2.12-3.0.0.tgz.asc\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003easc\u003c/em\u003e\u003c/a\u003e, \u003ca href=\"https://downloads.apache.org/kafka/3.0.0/kafka_2.12-3.0.0.tgz.sha512\" target=\"_blank\" rel=\"noreferrer\"\u003e\u003cem\u003esha512\u003c/em\u003e\u003c/a\u003e)\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"note-the-version-2133-is-not-working-and-will-throw-connection-refused-error\" class=\"relative group\"\u003e\u003cstrong\u003eNOTE\u003c/strong\u003e: The version 2.13.3 is not working and will throw Connection Refused error. \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#note-the-version-2133-is-not-working-and-will-throw-connection-refused-error\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h4\u003e\u003ch3 id=\"steps-to-setup\" class=\"relative group\"\u003e\u003cstrong\u003eSteps to Setup:\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#steps-to-setup\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eExtract the tgz file to some directory.\u003c/p\u003e","title":"Produce \u0026 Consume Messages Using Apache Kafka"},{"content":"In this post, we will try to understand the basics of the Reactive programming, what are the core features of it and how to handle the errors in reactive programming. This post will be more on the explanation section and less on coding something. That will come later.\nAccording to Wikipedia, Reactive Programming is defined as:\nReactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. This means that it becomes possible to express static (e.g. arrays) or dynamic (e.g. event emitters) data streams with ease via the employed programming language(s).\nReactive programming paradigm is often presented in object oriented languages as an extension of the Observer design pattern. It can also be compared to familiar iterator design pattern, with one difference where Iterator is pull based, while reactive streams are **push **based.\nReactive streams follows the Publisher-Subscriber pattern, where the publisher notifies the subscribers of newly available values as they come, and this push aspect is the key to being reactive. Also, operations applied to pushed values are expressed declaratively rather than imperatively. The programmer expressed the logic of the computation rather than describing its exact control flow.\nIn addition to pushing values, the error-handling and completion aspects are also covered in a well defined manner. A Publisher can push new values to its *Subscriber *(by calling onNext) but can also signal an error (by calling onError) or completion (by calling onComplete). Both errors and completion terminate the sequence.\nWhy Reactive #There is a detailed explanation about why it is used on the official site.\nBy writing asynchronous, non-blocking code, you let the execution switch to another active task that uses the same underlying resources and later comes back to the current process when the asynchronous processing has finished.\nReactor libraries, such as Reactor, aim to address these drawbacks of classic asynchronous approaches on the JVM:\nComposability and readability Data as a flow manipulated with a rich vocabulary of operators. Nothing happens until you subscribe Backpressure or the ability for the customer to signal thte producer that the rate of emission is too high. High level but high values abstraction that is concurrency-agnostics. Reactor Core Features #You can use the following maven dependency in your project to use reactive features.\n\u0026lt;!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.projectreactor\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;reactor-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.4.22\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; In reactive paradigm, there are two types of response\nSingle Object(Mono), or\nList of Objects(Flux)\nA Flux object represents a reactive sequence of 0..N items, while a *Mono *object represents a single-value-or-empty(0..1) result.\nFlux**, an Asynchronous Sequence of 0..N Items** #The following image shows how a Flux transforms items:\nA Flux is a standard Publisher that represents an asynchronous sequence of 0 to N emitted items, optionally terminated by either a completion signal or an error. As in the Reactive Streams spec, these three types of signal translate to calls to a downstream Subscriber’s onNext, onComplete, and onError methods.\nMono**, an Asynchronous 0-1 Result** #The following image shows how a Mono transforms an item:\nA Mono is a specialized Publisher that emits at most one item via the onNext signal then terminates with an onComplete signal (successful Mono, with or without value), or only emits a single onError signal (failed Mono).\nSimple ways to create Mono and Flux # Mono.empty() or Flux.empty() provide an empty Mono or Flux objects respectively. Mono.just(T) or Flux.just(T \u0026hellip;data) provide a mono of T object or Flux of T object respectively. You can find following options to create Mono or Flux from either Publisher, Supplier, Stream, Array or Iterables.\nHandling Errors #These items below will allow you to easily identify the scenario and which operator to use.\nI want to create an erroring sequence: error (Flux|Mono) to replace the completion of a successful Flux: .concat(Flux.error(e)) to replace the emission of a successful Mono): .then(Mono.error(e)) if too much time elapses between onNexts: timeout (Flux|Mono) lazily: error(Supplier) (Flux|Mono) I want the try/catch equivalent of: throwing: error **(Flux|Mono) catching an exception: and falling back to a default value: onErrorReturn **(Flux|Mono) and falling back to another Flux or Mono: onErrorResume (Flux|Mono) and wrapping and re-throwing: .onErrorMap(t → new RuntimeException(t)) (Flux|Mono) the finally block: doFinally (Flux|Mono) the using pattern from Java 7: using **(Flux|Mono) factory method I want to recover from errors by falling back: to a value: onErrorReturn (Flux|Mono) to a Publisher or Mono, possibly different ones depending on the error: Flux#onErrorResume and Mono#onErrorResume by retrying with a simple policy (max number of attempts): retry() (Flux|Mono), retry(long) (Flux|Mono) triggered by a companion control Flux: retryWhen (Flux|Mono) using a standard backoff strategy (exponential backoff with jitter): retryWhen(Retry.backoff(…​)) (Flux|Mono) (see also other factory methods in Retry) I want to deal with backpressure \u0026ldquo;errors\u0026rdquo; (request max from upstream and apply the strategy when downstream does not produce enough request) by throwing a special IllegalStateException: Flux#onBackpressureError by dropping excess values: Flux#onBackpressureDrop except the last one seen: Flux#onBackpressureLatest by buffering excess values (bounded or unbounded): Flux#onBackpressureBuffer and applying a strategy when bounded buffer also overflows: Flux#onBackpressureBuffer with a BufferOverflowStrategy Refer to the operators needed here\nReferences: https://projectreactor.io/docs/core/release/reference\nI hope this will help in understanding and getting a brief idea about the Reactive programming. Most of the content is taken from the official documentation with some explanation here and there. More on the reactive programming with the some examples will be covered in the upcoming posts.\nYou can find my Github* *profile. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/reactive-programming-in-java/","section":"Posts","summary":"\u003cp\u003eIn this post, we will try to understand the basics of the \u003cem\u003e\u003cstrong\u003eReactive programming\u003c/strong\u003e\u003c/em\u003e, what are the core features of it and how to handle the errors in reactive programming. This post will be more on the explanation section and less on coding something. That will come later.\u003c/p\u003e\n\u003cp\u003eAccording to Wikipedia, Reactive Programming is defined as:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eReactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. This means that it becomes possible to express static (e.g. arrays) or dynamic (e.g. event emitters) data streams with ease via the employed programming language(s).\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Reactive Programming in Java"},{"content":"This post will explore Apache POI library which is used to read any excel document and can also edit the excel sheets.\nApache POI (Poor Obfuscation Implementation) is open source java project to read or write Microsoft excel documents. The latest version is 5.0.1\nDependencies #To include Apache POI libraries, there are two libraries to handle different version of Excel.\nIf we are using MS Excel \u0026lsquo;97(-2007) file format, then you will be using HSSF(Horrible Spreadsheet Format) which is pure Java implementation of the Excel \u0026lsquo;97(-2007) file format.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.1.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; And If we are using Excel 2007 OOXML (.xlsx) file format then in order to read, write, access the sheets you will be using XSSF and need to add following dependency.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi-ooxml\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.0.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; You cannot use both the libraries together as, there will be some dependency which will become ambiguous on method call. Also there is another XSSF format called SXSSF which can be used when you are working with large spreadsheet and have to use low heap space.\nSXSSF achieves its low memory footprint by limiting access to the rows that are within a sliding window, while XSSF gives access to all rows in the document. Older rows that are no longer in the window become inaccessible, as they are written to the disk\nComposition #Like in any excel, here also you will work on a Workbook, which will contains Sheets. So sheets can be of either HSSF, or *XSSF *or *SXSSF *type. Similarly, each sheet contains *Row *and each row contains Cell. Read Excel File #We will read a given MS Excel file, which contains stock price dataset.\nCreate Workbook from Given Excel: The Workbook used here is the XSSFWorkbook which takes an Input Stream. Workbook workbook = new XSSFWorkbook(new FileInputStream(path)); Create Sheet or access the existing sheet using getSheetAt() method. XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0); Iterate over the Row to access each cell. for(Row row: sheet){...} Access Cell value using respective methods based on the data type of the cell. cell.getDateCellValue(); //Get the Date value from Cell cell.getNumericCellValue(); //Get the Numeric value from Cell cell.getStringCellValue(); //Get the String value from Cell Create cell if needed by using createCell() method of Row class based on index and also can set the cell value using the setter method in Cell class. Like below I am creating a new cell at index 7 and setting the value of the call as 876755.855. row.createCell(7).setCellValue(876755.855); Write to Excel File #To write to an excel file, we can use write() method of the Workbook class, which will write data to the Output Stream.\ntry(OutputStream os = new FileOutputStream(path)){ workbook.write(os); os.close(); } In the official documentation they have used OPCPackage to handle the lifecycle of the file, but when I used the OPCpackage to write or read the file, it threw following exception on closing the package.\nnested exception is org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException: Fail to save: an error occurs while saving the package : The part /docProps/app.xml failed to be saved in the stream with marshaller org.apache.poi.openxml4j.opc.internal.marshallers.DefaultMarshaller@5750bd37. Enable logging via POILogger for more details. There is no exact resolution to this, it might be because of the way OPCPackage is getting accessed and converting the data because inside the OPCPackage class, it is referring to the getParts() method where it iterates over the values and then un-marshal it. It could be due to something else also. If you know any details do comment down your suggestion.\nProject #So, we will be accessing a excel file containing Stock dataset and will try to read each stocks volume, high, low, open and close price for each day, and create new columns like\nTypical Price - Average of Open, High and Close price of each day VP - product of the volume and the typical price. TotalVP - summation of all the VP which came before. TotalV - summation of the Total volume. To calculate these specific columns we have to iterate over the cells and keep the summation of the value to a third variable.\nYou can find the above project in my github repository. You can directly check the process and the flow present in this class here.\nThe excel file is having around 800 records of stocks price which is taking around 2332ms ~ 2sec.\nReferences # https://poi.apache.org/components/spreadsheet/quick-guide.html#ReadWriteWorkbook http://poi.apache.org/components/spreadsheet/examples.html#common-formulas Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/read-write-excel-using-apache-poi-java/","section":"Posts","summary":"\u003cp\u003eThis post will explore Apache POI library which is used to read any excel document and can also edit the excel sheets.\u003c/p\u003e\n\u003cp\u003eApache POI (\u003cem\u003e\u003cstrong\u003ePoor Obfuscation Implementation\u003c/strong\u003e\u003c/em\u003e) is open source java project to read or write Microsoft excel documents. The latest version is \u003cstrong\u003e5.0.1\u003c/strong\u003e\u003c/p\u003e\n\u003ch3 id=\"dependencies\" class=\"relative group\"\u003e\u003cstrong\u003eDependencies\u003c/strong\u003e \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#dependencies\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h3\u003e\u003cp\u003eTo include Apache POI libraries, there are two libraries to handle different version of Excel.\u003c/p\u003e","title":"Read/Write Excel Using Apache POI \u0026 Java"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/6249579158f0bd10b2a57273/","section":"Categories","summary":"","title":"[\"6249579158f0bd10b2a57273\"]"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/%23226/","section":"Tags","summary":"","title":"#226"},{"content":"Following the last post on the* binary tree traversals*, in this post we will try to invert a given binary tree.\nBinary tree is a data structure in which a node can have maximum two child nodes and minimum zero nodes. Each node contains the value of the node, the left node reference and right node reference.\n4 / \\ 2 7 / \\ / \\ 1 3 6 9 In the above binary tree, root node has a value of 4 and 2 is the left node of the root and 7 is right node of the root. An inverted node will look like this, where the left node is swapped with the right node.\n4 / \\ 7 2 / \\ / \\ 9 6 3 1 Create the Node #Node can be used to construct a Binary Tree. We will be using the lombok library to avoid the boilerplate code. Using @Data annotation, we can avoid writing the constructors, setters and getters method.\n@Data class TreeNode{ private TreeNode left; private TreeNode right; private String value; } Prepare a binary tree #public TreeNode prepareBTree(){ TreeNode leaf1 = new TreeNode(\u0026#34;1\u0026#34;); TreeNode leaf2 = new TreeNode(\u0026#34;3\u0026#34;); TreeNode leaf3 = new TreeNode(\u0026#34;6\u0026#34;); TreeNode leaf4 = new TreeNode(\u0026#34;9\u0026#34;); TreeNode nodeRight = new TreeNode(leaf3, leaf4, \u0026#34;7\u0026#34;); TreeNode nodeLeft = new TreeNode(leaf1, leaf2, \u0026#34;2\u0026#34;); TreeNode root = new TreeNode(nodeLeft, nodeRight, \u0026#34;4\u0026#34;); return root; } Invert Binary Tree (Algorithm/Logic) #To invert the binary tree, we will use recursion and use the same logic we use for swapping two values. Using a third Node, we will swap the left and right nodes with each other.\nIn the below method, we are recursively going to each sub-binary tree and swapping the left node and right node with each other. Here the exit criteria will be leaf node. Leaf nodes don\u0026rsquo;t have any left or right node. So, if a node.left is null or node.right is null, then the program will return the null.\npublic TreeNode invertBTree(TreeNode node){ if(null==node){ return null; } if(null==node.getLeft() \u0026amp;\u0026amp; null==node.getRight()){ return node; } TreeNode temp = node.getLeft(); node.setLeft(node.getRight()); node.setRight(temp); invertBTree(node.getLeft()); invertBTree(node.getRight()); return node; } You can find the whole code here. Github Link\nPlease do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/lc226-inversion-of-binary-tree-using-recursion/","section":"Posts","summary":"\u003cp\u003eFollowing the last post on the\u003ca href=\"https://www.dynamicallyblunttech.com/post/binary-tree-traversal\" target=\"_blank\" rel=\"noreferrer\"\u003e* binary tree traversals*\u003c/a\u003e, in this post we will try to invert a given binary tree.\u003c/p\u003e\n\u003cp\u003eBinary tree is a data structure in which a node can have maximum two child nodes and minimum zero nodes. Each node contains the value of the node, the left node reference and right node reference.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     4\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e   \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  2     7\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e   \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1   3 6   9\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the above binary tree, root node has a value of 4 and 2 is the left node of the root and 7 is right node of the root. \u003cem\u003e\u003cstrong\u003eAn inverted node will look like this, where the left node is swapped with the right node.\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e","title":"#LC226 - Inversion Of Binary Tree Using Recursion"},{"content":"Part 1 of the StreamMetrics Series\n*Previous parts: Part 2: Consumer + DLQ | Part 3: Real-Time Aggregations | Part 4: From Localhost to Kubernetes\nLove how it rhymes \u0026ldquo;Production Kafka Producer\u0026rdquo;.\nHere\u0026rsquo;s to coding in an era of AI Agents. They suggest and are confident that this time it will work, but then this cluster has its own plan, and it starts misbehaving \u0026ndash; sometimes listens, and on others, feels cornered by his other siblings. This is the Tale of making the brothers listen and show how the configurations works when we enter the world of multi-node cluster in Kafka and Spring.\nStream Metrics: #StreamMetrics is a real-time analytics platform that processes application metrics from multiple Spring Boot microservices using Kafka, providing sub-second visibility into system health and performance. High Level Architecture So, Objective for this post is to establish a 3 node Kafka cluster, which enable the Producer to produce some messages. Consumer will come later in this series.\nTechnology Stack #Core Infrastructure # Kafka: 3.6.0 (Multi-broker cluster) Zookeeper: 3.8.0 (Kafka dependency) Redis: 7.2 (In-memory cache) PostgreSQL: 15 (Time-series extension) Application Stack # Java: 21 (LTS) Spring Boot: 3.2.x Spring Kafka: 3.1.x Kafka Streams: 3.6.x Spring Data JPA: Latest Spring Data Redis: Latest Supporting Tools # Docker \u0026amp; Docker Compose: Local dev environment Testcontainers: Integration testing JUnit 5: Unit testing Micrometer: Metrics collection Lombok: Reduce boilerplate Project Structure: # Project Structure Main Module: #This is the main module, which will have sub modules like:\nstreammetric-common (common module - which contains the sede) streammetric-producer (Kafka Producer) streammetric-consumer (Kafka Consumer) streammetric-query (Query Layer) streammetric-ui (Vaadin or React UI) Here is the pom file for this module:\n\u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.0.2\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;groupId\u0026gt;org.backendbrilliance\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;streammetrics\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.0.1-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;pom\u0026lt;/packaging\u0026gt; \u0026lt;modules\u0026gt; \u0026lt;module\u0026gt;streammetrics-common\u0026lt;/module\u0026gt; \u0026lt;module\u0026gt;streammetrics-producer\u0026lt;/module\u0026gt; \u0026lt;!-- \u0026lt;module\u0026gt;streammetrics-consumer\u0026lt;/module\u0026gt;--\u0026gt; \u0026lt;!-- \u0026lt;module\u0026gt;streammetrics-query\u0026lt;/module\u0026gt;--\u0026gt; \u0026lt;!-- \u0026lt;module\u0026gt;streammetrics-ui\u0026lt;/module\u0026gt;--\u0026gt; \u0026lt;/modules\u0026gt; Challenges: # Issue: Using Java 25 (early access) caused TypeTag::UNKNOWN compilation errors Root cause: Maven compiler plugin + Lombok incompatibility Fix: Downgraded to Java 21 LTS streammetrics-common module: #This is the simple spring boot application where we define the models and the (sede) se/de-serializers for our Kafka.\nWe have following modules defined:\nMetricEvent (message for Kafka) MetricType (Enum, which tells what kind of MetricEvent it is sending) Lets check the pom.xml for common module:\n\u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.backendbrilliance\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;streammetrics\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.0.1-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;artifactId\u0026gt;streammetrics-common\u0026lt;/artifactId\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;java.version\u0026gt;21\u0026lt;/java.version\u0026gt; \u0026lt;kafka.version\u0026gt;4.1.1\u0026lt;/kafka.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.fasterxml.jackson.core\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jackson-databind\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.fasterxml.jackson.datatype\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jackson-datatype-jsr310\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;jakarta.validation\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jakarta.validation-api\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.kafka\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;kafka-clients\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${kafka.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.junit.jupiter\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;junit-jupiter\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; Here is the MetricEvent (Using lombok to avoid boiler-plate code)\n/** * Represents a single metric event in the system. * This is the core data model sent through Kafka. */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class MetricEvent { @NotBlank(message = \u0026#34;Event ID cannot be blank\u0026#34;) @JsonProperty(\u0026#34;event_id\u0026#34;) private String eventId; @NotNull(message = \u0026#34;Timestamp cannot be null\u0026#34;) @JsonProperty(\u0026#34;timestamp\u0026#34;) private Instant timestamp; @NotBlank(message = \u0026#34;Service ID cannot be blank\u0026#34;) @JsonProperty(\u0026#34;service_id\u0026#34;) private String serviceId; @NotBlank(message = \u0026#34;Instance ID cannot be blank\u0026#34;) @JsonProperty(\u0026#34;instance_id\u0026#34;) private String instanceId; @NotNull(message = \u0026#34;Metric type cannot be null\u0026#34;) @JsonProperty(\u0026#34;metric_type\u0026#34;) private MetricType metricType; @NotBlank(message = \u0026#34;Metric name cannot be blank\u0026#34;) @JsonProperty(\u0026#34;metric_name\u0026#34;) private String metricName; @NotNull(message = \u0026#34;Value cannot be null\u0026#34;) @Positive(message = \u0026#34;Value must be positive\u0026#34;) @JsonProperty(\u0026#34;value\u0026#34;) private Double value; @JsonProperty(\u0026#34;unit\u0026#34;) private String unit; @JsonProperty(\u0026#34;tags\u0026#34;) private Map\u0026lt;String, String\u0026gt; tags; @JsonProperty(\u0026#34;metadata\u0026#34;) private Map\u0026lt;String, String\u0026gt; metadata; } And this is MetricType Enum:\npublic enum MetricType { HTTP_REQUEST, DATABASE_QUERY, CACHE_HIT, CACHE_MISS, EXTERNAL_API_CALL, MESSAGE_QUEUE_PUBLISH, MESSAGE_QUEUE_CONSUME, CUSTOM_COUNTER, CUSTOM_GAUGE, CUSTOM_HISTOGRAM } Then comes the serializers and deserializers which will be mapped to Kafka configuration.\nThe Serializer will import org.apache.kafka.common.serialization.Serializer and Deserializer will import org.apache.kafka.common.serialization.Deserializer. Challenges: # Issue: UnsatisfiedDependencyException - Spring trying to autowire beans in common module Root cause: Common module had Spring Boot dependencies (should be plain Java library) Fix: Removed Spring dependencies from common, moved tests to producer streammetric-producer module: #This module is the Kafka producer, that will be produce the messages to different topics.\nDefine the ProducerFactory Create KafkaTemplate ProducerFactory: # Producer Factory Kafka Template:\n@Bean public KafkaTemplate\u0026lt;String, MetricEvent\u0026gt; kafkaTemplate() { return new KafkaTemplate\u0026lt;\u0026gt;(producerFactory()); } Then we will use the kafkaTemplate to send the data to specific topic. SendMetric Retry Implementation: # This annotation can be applied to a class or a specific method. Applying it on a class is equivalent to applying it on all its public methods. The annotation enables backend retry for all methods where it is applied. Backend retry is performed via a retry. If using Spring, name and fallbackMethod can be resolved using Spring Expression Language (SpEL).\nCircuitBreaker: # This annotation can be applied to a class or a specific method. Applying it on a class is equivalent to applying it on all its public methods. The annotation enables backend monitoring for all methods where it is applied. Backend monitoring is performed via a circuit breaker. See io.github.resilience4j.circuitbreaker.CircuitBreaker for details. If using Spring, name and fallbackMethod can be resolved using Spring Expression Language (SpEL).\nThis producer will send the message and logs it, without doing much for now.\nLets check the application.yml configuration needed with a 3 node cluster.\nspring: application: name: streammetrics-producer kafka: bootstrap-servers: localhost:9092,localhost:9094,localhost:9096 producer: # Serializers key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.backendbrilliance.streammetrics.serde.MetricEventSerializer # Reliability settings acks: all retries: 2147483647 # Infinite retries max-in-flight-requests-per-connection: 5 enable-idempotence: true # Performance tuning compression-type: snappy batch-size: 32768 # 32KB linger-ms: 10 buffer-memory: 67108864 # 64MB # Timeout settings request-timeout-ms: 30000 delivery-timeout-ms: 120000 # Additional properties properties: max.block.ms: 60000 Check the performance tuning part, where the batch size is defined with the compression type for each message. This restricts a huge message blocking others.\nThe bootstrap servers are the three nodes of the Kafka cluster.\nThe circuit breaker and retry mechanism can be configured like this:\nresilience4j: retry: instances: kafkaProducer: max-attempts: 3 wait-duration: 1s exponential-backoff-multiplier: 2 retry-exceptions: - org.apache.kafka.common.errors.NetworkException - org.apache.kafka.common.errors.TimeoutException ignore-exceptions: - org.apache.kafka.common.errors.SerializationException circuitbreaker: instances: kafkaProducer: failure-rate-threshold: 50 wait-duration-in-open-state: 10s sliding-window-size: 10 minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 The naming is self explanatory. If more info is needed, check the official documentation.\nNow, lets talk about the elephant in the room.\nSince we are creating a Kafka cluster with 3 nodes, we need to define the Kafka network, port mapping, advertised listeners, clusterId, replication factor which are mandatory for the cluster to work properly.\nHere is the part of docker-compose file, which configures Kafka Node1:\nservices: kafka1: image: confluentinc/cp-kafka:7.8.0 hostname: kafka1 container_name: kafka1 ports: - \u0026#34;9092:9092\u0026#34; - \u0026#34;9093:9093\u0026#34; environment: KAFKA_NODE_ID: 1 KAFKA_BROKER_ID: 1 KAFKA_PROCESS_ROLES: \u0026#39;broker,controller\u0026#39; KAFKA_CONTROLLER_QUORUM_VOTERS: \u0026#39;1@kafka1:9093,2@kafka2:9093,3@kafka3:9093\u0026#39; KAFKA_LISTENERS: \u0026#39;PLAINTEXT://kafka1:19092,EXTERNAL://0.0.0.0:9092,CONTROLLER://kafka1:9093\u0026#39; KAFKA_ADVERTISED_LISTENERS: \u0026#39;PLAINTEXT://kafka1:19092,EXTERNAL://localhost:9092\u0026#39; KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: \u0026#39;PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT\u0026#39; KAFKA_CONTROLLER_LISTENER_NAMES: \u0026#39;CONTROLLER\u0026#39; KAFKA_INTER_BROKER_LISTENER_NAME: \u0026#39;PLAINTEXT\u0026#39; CLUSTER_ID: \u0026#39;EmptNWtoR4GGWx-BH6nGLQ\u0026#39; KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 KAFKA_DEFAULT_REPLICATION_FACTOR: 3 KAFKA_MIN_INSYNC_REPLICAS: 2 volumes: - ./kafka1/data:/var/lib/kafka/data networks: - streammetrics-network Here\nPLAINTEXT on port 19092 (internal, broker-to-broker communication) EXTERNAL on port 9092 (external, for host machine via localhost) CONTROLLER on port 9093 (for KRaft consensus) Refer to my Github repo to get the entire docker-compose.yml file to setup the 3 node kafka cluster.\nChallenges Faced: # Issue: UnknownHostException: kafka1,2,3, then Connection to node could not be established Root causes: Kafka containers advertising internal hostnames (kafka1:9092) instead of localhost Missing EXTERNAL listeners for host machine access Port mapping mismatch (9094:9092 instead of 9094:9094) Fix: Added dual listeners (INTERNAL for broker-to-broker, EXTERNAL for localhost) Another issue was with Stale data\nIssue: DUPLICATE_BROKER_REGISTRATION errors Root cause: Old cluster metadata in /data directories Fix: Cleaned kafka1/2/3 data directories before restart Kafka UI #We have setup Kafka UI also to refer for all the brokers assigned, topics created and the messages producer vs consumed. Brokers Can be accessed via: http://localhost:8080/ui/clusters/local/brokers\nThis is how we setup Kafka-UI through docker-compose:\nkafka-ui: image: provectuslabs/kafka-ui:latest container_name: kafka-cluster-ui ports: - \u0026#34;8080:8080\u0026#34; environment: KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka1:19092,kafka2:19092,kafka3:19092 DYNAMIC_CONFIG_ENABLED: \u0026#39;true\u0026#39; depends_on: - kafka1 - kafka2 - kafka3 networks: - streammetrics-network This is the Kafka UI after producer sends 300 messages: Message Count Mind Map Outcome: ✅ Success # 3-broker Kafka cluster running in KRaft mode (no Zookeeper) Producer sending messages with idempotent + exactly-once semantics 300+ messages verified in Kafka UI Replication factor: 3, min in-sync replicas: 2 Custom serializers working (MetricEventSerializer) Project structure properly set up (parent + common + producer modules) Next part will have the Kafka consumer with manual offset commits, implement dead letter queue for failed messages and proper error handling and test end to end producer \u0026ndash;\u0026gt; consumer flow.\nYou can find my\nGithub: https://github.com/ankitagrahari Instagram: https://www.instagram.com/backendbrilliance/ Medium: https://medium.com/@ankitagrahari.rkgit Share your thoughts whether you liked or disliked it. Do let me know if you having any queries or suggestions. Never forget, Learning is the primary goal.\nLearn with every commit.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/building-a-production-kafka-producer/","section":"Posts","summary":"\u003cp\u003e\u003cstrong\u003ePart 1 of the StreamMetrics Series\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e*Previous parts: \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/building-production-grade-kafka-consumer-patterns/\"\u003e\u003cem\u003ePart 2: Consumer + DLQ\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/real-time-aggregations-with-kafka-streams-at-10k-events-sec/\"\u003e\u003cem\u003ePart 3: Real-Time Aggregations\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e | \u003cem\u003e\n      \n    \u003ca href=\"https://dynamicallyblunttech.com/posts/from-localhost-to-kubernetes-deploying-streammetrics-at-scale/\"\u003e\u003cem\u003ePart 4: From Localhost to Kubernetes\u003c/em\u003e\u003c/a\u003e\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eLove how it rhymes \u0026ldquo;Production Kafka Producer\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s to coding in an era of AI Agents. They suggest and are confident that this time it will work, but then this cluster has its own plan, and it starts misbehaving \u0026ndash; sometimes listens, and on others, feels cornered by his other siblings. This is the \u003cem\u003e\u003cstrong\u003eTale\u003c/strong\u003e\u003c/em\u003e of making the brothers listen and show how the configurations works when we enter the world of multi-node cluster in Kafka and Spring.\u003c/p\u003e","title":"Building a Production Kafka Producer"},{"content":"If you have seen lately, everyone has shifted to Java 8 and is more or less trying to move from lengthy code blocks to few lines/precise use of Lambda and Functional Programming introduced in Java 8. So, we will talk about all day to day operations that can be converted to use lambda or functional programming and make our code precise and simple.\nThis post will be a bit lengthy where we will write Junit5 tests and the code for every scenario. It will be interesting to know, as these days, a lot of companies are focusing on candidates to know the use of lambda expressions at least.\nLets start considering we have a User object which has its attributes like firstName, lastName, *age *and a set of Privileges. The Privileges is an enum, which defines all privileges a user can have, like Read, Create, Update, Delete.\nSo my User and Privilege class looks like this:\nUser.java\npublic class User { private Long id; private String firstName; private String lastName; private Integer age; private List\u0026lt;Privilege\u0026gt; privileges; public User(final Long id, final String firstName, final String lastName, final Integer age, final List\u0026lt;Privilege\u0026gt; privileges) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.age = age; this.privileges = Collections.unmodifiableList(privileges); } . . //getters and setters for its attributes . . @Override public String toString(){ return this.id+\u0026#34;_\u0026#34; + this.firstName + \u0026#34;_\u0026#34; + this.lastName + \u0026#34;_\u0026#34; + this.age + \u0026#34;_(\u0026#34; + ((this.privileges.size()==4) ? \u0026#34;All\u0026#34; : this.privileges.stream() .map(String::valueOf) .collect(Collectors.joining(\u0026#34;,\u0026#34;)) ) + \u0026#34;)\u0026#34;; } } Privilege.java\npublic enum Privilege { CREATE, UPDATE, READ, DELETE } The User class does not override *hashcode *and *equals *method.\nLets start with each scenario one by one.\nScenarios #1. Sort By Name in Ascending and Age in Descending Order #We need to sort the list of users\nby first name, then\nby last name, and then\nby age in reverse order.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Sort Users by Name in ascending and then by Age in descending order\u0026#34;) void sortByAgeDescAndNameAsc() { final User user1 = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, ALL_PRIVILEGES); final User user2 = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, ALL_PRIVILEGES); final User user3 = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, ALL_PRIVILEGES); final User user4 = new User(4L, \u0026#34;Dynamic\u0026#34;, \u0026#34;Blunt\u0026#34;, 30, Collections.singletonList(Privilege.READ)); final User user5 = new User(5L, \u0026#34;Dynamic\u0026#34;, \u0026#34;BluntTech\u0026#34;, 30, ALL_PRIVILEGES); final List\u0026lt;User\u0026gt; sortedUsers = userService.sortByAgeDescAndNameAsc( Arrays.asList(user1, user2, user3, user4, user5) ); System.out.println(sortedUsers); Assertions.assertArrayEquals( sortedUsers.toArray(), new User[]{ user4, user5, user2, user1, user3} ); } We have 5 users, with\nGreg and Alex user with same last name and different age Dynamic Blunt and Dynamic BluntTech user with same first name and age but different last name. We are asserting and expecting the users to be in this order\nuser4(Dynamic Blunt) --\u0026gt; user5(Dynamic BluntTech) --\u0026gt; user2(Greg) --\u0026gt; user1(John) --\u0026gt; user3(Alex) Lets check the logic for this: #We need to use lambda expression to first sort it based on first name, then last name and then the age in reverse order. So we do what we say:\n@Override public List\u0026lt;User\u0026gt; sortByAgeDescAndNameAsc(List\u0026lt;User\u0026gt; users) { return users.stream() .sorted( Comparator.comparing(User::getFirstName) .thenComparing(User::getLastName) ) .peek(System.out::print) .sorted( Comparator.comparingInt(User::getAge) .reversed() ) .peek(System.out::println) .collect(Collectors.toList()); } On the stream of users, we are using Comparator.comparing() method to sort. We also have to tell on what basis it has to compare and for that we used Functional programming to get the first name (getFirstName()). Once we have compared on first name, then we have to compare on the last name, and for that we used thenComparing() method.\npeek() takes Consumer as argument and we can use this to know the values from the previous lambda operation. Very useful in debugging.\nSimilarly, we can sort based on the age, but this time we will use reversed(), as we need the age to sorted in descending order.\nAnd at last, we have the terminal operation, which will collect the sorted users in a List.\nSo the output on console will have the print statements from peek() and final list.\n3_Alex_Smith_13_(All)4_Dynamic_Blunt_30_(READ)5_Dynamic_BluntTech_30_(All)2_Greg_Smith_30_(All)1_John_Doe_26_(All)4_Dynamic_Blunt_30_(READ) 5_Dynamic_BluntTech_30_(All) 2_Greg_Smith_30_(All) 1_John_Doe_26_(All) 3_Alex_Smith_13_(All) ----------------------------------------------\u0026gt;\u0026gt;Above are from Peek() [4_Dynamic_Blunt_30_(READ), 5_Dynamic_BluntTech_30_(All), 2_Greg_Smith_30_(All), 1_John_Doe_26_(All), 3_Alex_Smith_13_(All)] 2. Distinct Privileges #We need to return all the distinct privileges which are part of the input list of User.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Return all distinct privilege in the list\u0026#34;) void getAllDistinctPrivileges() { final User createUser = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.CREATE)); final User updateUser = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, Collections.singletonList(Privilege.UPDATE)); final User updateUser1 = new User(3L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 20, Collections.singletonList(Privilege.UPDATE)); final User deleteUser = new User(4L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); final List\u0026lt;Privilege\u0026gt; distinctPrivileges = userService.getAllDistinctPrivileges( Arrays.asList( createUser, updateUser, updateUser1, deleteUser ) ); System.out.println(distinctPrivileges); Assertions.assertEquals(distinctPrivileges.size(), 3); } We have 4 users, which are having Create, Update and Delete as the distinct privileges and the output size will be the assertion criteria.\nLets look into the logic of how to get the List\u0026lt;List\u0026lt;\u0026raquo; to List\u0026lt;\u0026gt; using* flatMap()*. #@Override public List\u0026lt;Privilege\u0026gt; getAllDistinctPrivileges(List\u0026lt;User\u0026gt; users) { return users.stream() .map(user -\u0026gt; user.getPrivileges()) .peek(System.out::print) .flatMap(List::stream) .distinct() .peek(System.out::println) .toList(); } See, how precise and easy to understand this code looks. We are first getting the list of privileges for each user and then converting that List to Stream of Privilege. Doing a distinct() and converting to list will be our output.\nMore intuitive it will be, if you are using some editor, and properly separating it to new line after every lambda operation. I am using IntelliJ, and this is how it looks\nYou can refer the yellow marked ones, where the editor is displaying the return type of that operation. Like,\nuser.stream() returns a *Stream *\nthen applying map() on it, returns a Stream\u0026lt;List\u0026gt;\nthen applying flatMap() on it, will give me a Stream.\nSo, the output of this method will be:\n[CREATE]CREATE [UPDATE]UPDATE [UPDATE][DELETE]DELETE ----------------------------------------------\u0026gt;\u0026gt;Above are from peek() [CREATE, UPDATE, DELETE] 3. Filter User with Update Privilege and Age\u0026gt;29 #We will be filtering the list of user with Update Privilege and Age greater than given input(29).\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Return User with higher age than X of Update Privilege\u0026#34;) void getUpdateUserWithAgeHigherThan() { final User updateUser = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.UPDATE)); final User updateUser1 = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, Collections.singletonList(Privilege.UPDATE)); final User deleteUser = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); Optional\u0026lt;User\u0026gt; user = userService.getUpdateUserWithAgeHigherThan( Arrays.asList(updateUser1, updateUser, deleteUser), 29 ); System.out.println(user.get()); Assertions.assertEquals(user.get(), updateUser1); } I have 3 Users, of which 2 are having Update Privilege, and only one is having Age greater than 29. So my assertion criteria will be on that User.\nLets check the logic for this: #@Override public Optional\u0026lt;User\u0026gt; getUpdateUserWithAgeHigherThan(List\u0026lt;User\u0026gt; users, int age) { return users.stream() .filter(user -\u0026gt; user.getAge() \u0026gt; age \u0026amp;\u0026amp; user.getPrivileges().contains(Privilege.UPDATE) ) .peek(System.out::println) .findFirst(); //Can use both findFirst() or findAny() } If you notice, we are returning an Option because, there might be a scenario where the given filter constraints never gets fulfilled, and in this scenario will return Optional.empty.\nTerminal operation like findFirst(), findAny(), average() etc return *Optional *object, which uses isPresent() to return false, if the return value is null, else true.\nOutput of this test is this\n2_Greg_Smith_30_(UPDATE) ----------------------------------------------\u0026gt;\u0026gt;Above are from peek() 2_Greg_Smith_30_(UPDATE) 4. Group Users By Count of Privileges #We have to find the users who have either one Privilege, or two or three or all 4 Privilege.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Count of Users for each Privilege\u0026#34;) void groupByCountOfPrivileges() { final int ONE_PRIVILEGE = 1; final int TWO_PRIVILEGES = 2; final int FOUR_PRIVILEGES = 4; final User userWith2Privileges = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Arrays.asList(Privilege.UPDATE, Privilege.CREATE)); final User userWith4Privileges = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, ALL_PRIVILEGES); final User userWith4Privileges1 = new User(2L, \u0026#34;Anton\u0026#34;, \u0026#34;Smith\u0026#34;, 34, ALL_PRIVILEGES); final User userWith1Privileges1 = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); final User userWith1Privileges2 = new User(3L, \u0026#34;Neo\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.UPDATE)); Map\u0026lt;Integer, List\u0026lt;User\u0026gt;\u0026gt; privilegesCountUsersMap = userService .groupByCountOfPrivileges( Arrays.asList( userWith1Privileges1, userWith1Privileges2, userWith2Privileges, userWith4Privileges, userWith4Privileges1 ) ); System.out.println(privilegesCountUsersMap); Assertions.assertArrayEquals( new int[]{2, 1, 2}, new int[]{ privilegesCountUsersMap.get(ONE_PRIVILEGE).size(), privilegesCountUsersMap.get(TWO_PRIVILEGES).size(), privilegesCountUsersMap.get(FOUR_PRIVILEGES).size() } ); } So, we have 5 Users, which has either 1 or 2 or 3 Privileges. The method will be returning a Map of Count of Privileges and List of Users. My assertion criteria will be to look each element of map, and assert on the size of User list for each privilege.\nLets check the logic of this: #@Override public Map\u0026lt;Integer, List\u0026lt;User\u0026gt;\u0026gt; groupByCountOfPrivileges(List\u0026lt;User\u0026gt; users) { return users.stream() .collect( Collectors.groupingBy( user -\u0026gt; user.getPrivileges().size() ) ); } We only have to group by the list of users based on the size of the Privileges each user has. And so we used Collectors.groupingBy() on the Privilege list size.\nOutput of the test is:\n{1=[3_Alex_Smith_13_(DELETE), 3_Neo_Smith_13_(UPDATE)], 2=[1_John_Doe_26_(UPDATE,CREATE)], 4=[2_Greg_Smith_30_(All), 2_Anton_Smith_34_(All)]} 5. Count of Privileges for each User #We have to return the count of Privileges each user possess. This scenario is very similar to the above one, where we have to find the count of User for each Privilege.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Count of Privileges per User\u0026#34;) void countOfPrivilegesPerUser() { final User user1With2Privileges = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Arrays.asList(Privilege.UPDATE, Privilege.CREATE)); final User user2With4Privileges = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, ALL_PRIVILEGES); final User user3With4Privileges = new User(2L, \u0026#34;Anton\u0026#34;, \u0026#34;Smith\u0026#34;, 34, ALL_PRIVILEGES); final User user4With1Privilege = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); final User user5With1Privilege = new User(3L, \u0026#34;Neo\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.UPDATE)); Map\u0026lt;User, Integer\u0026gt; privilegesCountPerUserMap = userService .countOfPrivilegesPerUser( Arrays.asList( user4With1Privilege, user5With1Privilege, user1With2Privileges, user2With4Privileges, user3With4Privileges ) ); System.out.println(privilegesCountPerUserMap); Assertions.assertArrayEquals( new int[]{2, 4, 4, 1, 1}, new int[]{ privilegesCountPerUserMap.get(user1With2Privileges), privilegesCountPerUserMap.get(user2With4Privileges), privilegesCountPerUserMap.get(user3With4Privileges), privilegesCountPerUserMap.get(user4With1Privilege), privilegesCountPerUserMap.get(user5With1Privilege) } ); } We have 5 users, which will have different privileges, and we have to assert on the Map returned by the method, which holds the count of Privilege each User has. Map is of User and the count of Privileges.\nLets check the logic to get this: #@Override public Map\u0026lt;User, Integer\u0026gt; countOfPrivilegesPerUser(List\u0026lt;User\u0026gt; users) { return users.stream() .collect( Collectors.groupingBy( user -\u0026gt; user, Collectors.summingInt( value -\u0026gt; value.getPrivileges().size() ) ) ); } We have the same logic applied as previous one, but this time, we are using Collectors.summingInt() method to define on what basis the Collectors.groupingBy() will group the users.\nOutput of this test is:\n{ 3_Alex_Smith_13_(DELETE)=1, 2_Greg_Smith_30_(All)=4, 1_John_Doe_26_(UPDATE,CREATE)=2, 2_Anton_Smith_34_(All)=4, 3_Neo_Smith_13_(UPDATE)=1 } 6. Average Age of Users #We have to return the average age of all the users. The return type should be double.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Return average age of Users\u0026#34;) void getAverageAgeForUsers() { final User createUser = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.CREATE)); final User updateUser = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, Collections.singletonList(Privilege.UPDATE)); final User updateUser1 = new User(3L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 20, Collections.singletonList(Privilege.UPDATE)); final User deleteUser = new User(4L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); double averageAge = userService.getAverageAgeForUsers( Arrays.asList(createUser, updateUser, updateUser1, deleteUser) ); System.out.println(averageAge); Assertions.assertEquals(averageAge, 22.25); } We have 4 Users, whose age is given and we need to assert on the average age of the users.\nLets check the logic of this: #@Override public double getAverageAgeForUsers(List\u0026lt;User\u0026gt; users) { return users.stream() .mapToDouble(User::getAge) .average() .getAsDouble(); } We have mapped the User stream to its attributes *Age *and called the terminal operation average(), which will return an *Optional *object. To get the values from *Optional *object, we can use the get() method. Here, to get the double value, we have used getAsDouble().\nOutput of the test is:\n22.25 7. Most Frequent Last Name of Users #We have to find the Last name from the list of User, which has occurred most number of times in the list.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Return most frequent occurring last name from the list\u0026#34;) void getMostFrequentLastName() { final User createUser = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.CREATE)); final User updateUser = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 30, Collections.singletonList(Privilege.UPDATE)); final User updateUser1 = new User(3L, \u0026#34;Greg\u0026#34;, \u0026#34;Smith\u0026#34;, 20, Collections.singletonList(Privilege.UPDATE)); final User deleteUser = new User(4L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); String lastName = userService.getMostFrequentLastName( Arrays.asList(createUser, updateUser, updateUser1, deleteUser) ).get(); System.out.println(lastName); Assertions.assertEquals(lastName, \u0026#34;Smith\u0026#34;); } We have 4 Users, among which Smith is the last name for 3 Users. So our assertions criteria will also be the same.\nLet check the logic for this: #A normal operation would be something like, getting the map of last name and the count of it. But we can do this in one line.\n@Override public Optional\u0026lt;String\u0026gt; getMostFrequentLastName(List\u0026lt;User\u0026gt; users) { Map\u0026lt;String, Long\u0026gt; map = users.stream() .collect(Collectors.groupingBy( User::getLastName, Collectors.counting() )); return Optional.of( map.entrySet() .stream() .max((e1, e2) -\u0026gt; e1.getValue()\u0026lt;e2.getValue()?-1:1) .get() .getKey() ); } In the map, we have grouped the Users by Last Name and captured the count of the last name found in the list. Then from the Map, we have found the max() by passing the comparator logic to get the highest count value first. Since max() is also a terminal operation, it will return Optional object.\nYou can see the return type of each operation here:\nOutput is:\nSmith 8. Filter by Multiple Predicates\nWe are given with multiple filter constraints, and we have to apply all the filters in the same user Stream.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Filter Users by first name length \u0026gt; 3 and privileges \u0026gt; 2\u0026#34;) void filterBy() { final User user1 = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.UPDATE)); final User user2 = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Jonson\u0026#34;, 30, Arrays.asList( Privilege.UPDATE, Privilege.CREATE, Privilege.DELETE )); final User user3 = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); Predicate\u0026lt;User\u0026gt; firstNameLongerThan3 = user -\u0026gt; user.getFirstName().length()\u0026gt;3; Predicate\u0026lt;User\u0026gt; privilegeGreaterThan2 = user -\u0026gt; user.getPrivileges().size()\u0026gt;2; List\u0026lt;User\u0026gt; users = userService.filterBy( Arrays.asList(user1, user2, user3), firstNameLongerThan3, privilegeGreaterThan2 ); System.out.println(users); Assertions.assertEquals(users.size(), 1); } We have 3 Users, on which we two Predicates/Filters applied, and our assertion criteria will also be same.\n*Predicate *is a *boolean *valued one argument functional Interface, which has test() as the functional method.\nLets check the logic to get this: #@Override public List\u0026lt;User\u0026gt; filterBy(List\u0026lt;User\u0026gt; users, Predicate\u0026lt;User\u0026gt;... predicates) { Predicate\u0026lt;User\u0026gt; allPredicates = Arrays.stream(predicates) .reduce(w-\u0026gt;true, Predicate::and); return users.stream() .filter(allPredicates) .collect(Collectors.toList()); } In order to club all the predicates together, we will create a new Predicate, which will take the input list of all predicate and reduce it with an AND operation. We can also use OR operation as well.\nThe w\u0026ndash;\u0026gt; true is the initial value, so now when we club together all predicates it would be something like this.\ntrue AND Predicate1_Output AND Predicate2_Output AND.... Output for this scenario is:\n[2_Greg_Jonson_30_(UPDATE,CREATE,DELETE)] We can have one more scenario where there are no predicates or filters applies. When we don\u0026rsquo;t have any filter, it will return the same list as output.\nLets check the Junit for Empty Filters: #@Test @DisplayName(\u0026#34;Return same list if found no filterBy conditions(predicates)\u0026#34;) void emptyFilter() { final User user1 = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.UPDATE)); final User user2 = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Jonson\u0026#34;, 30, Arrays.asList( Privilege.UPDATE, Privilege.CREATE, Privilege.DELETE )); final User user3 = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); List\u0026lt;User\u0026gt; users = userService.filterBy( Arrays.asList(user1, user2, user3) ); System.out.println(users); Assertions.assertEquals(users.size(), 3); } In this we don\u0026rsquo;t have any filters applied, which will return the same input list as output.\nOutput is:\n[ 1_John_Doe_26_(UPDATE), 2_Greg_Jonson_30_(UPDATE,CREATE,DELETE), 3_Alex_Smith_13_(DELETE) ] 9. Map given Function with Delimiter #We are given with a function which we need to apply for the list of User and join it based on the delimiter provided.\nLets the check the Junit for this scenario: #@Test @DisplayName(\u0026#34;Convert Users by applying Function and delimited by |\u0026#34;) void convertTo() { final User user1 = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.emptyList()); final User user2 = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Jonson\u0026#34;, 30, Collections.emptyList()); final User user3 = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.emptyList()); Function\u0026lt;User, String\u0026gt; function = User::getLastName; String convertedStr = userService.convertTo( Arrays.asList(user1, user2, user3), \u0026#34;|\u0026#34;, function ); System.out.println(convertedStr); Assertions.assertEquals(convertedStr, \u0026#34;Doe|Jonson|Smith\u0026#34;); } We have 3 Users, and a Function, where we have to map the User stream to each User\u0026rsquo;s last name. Then return the String joined by pipe delimiter. And this String will be out assertion criteria as well.\nFunction is a functional Interface which takes one argument and produces the result. It has apply() as the functional method.\nLike the previous one, where we have to apply Predicates, here will be mapping the Function given.\nLets check the logic for this: #@Override public String convertTo(List\u0026lt;User\u0026gt; users, String delimiter, Function\u0026lt;User, String\u0026gt; mapFun) { return users.stream() .map(mapFun) .peek(System.out::println) .collect(Collectors.joining(delimiter)); } Here, we are using map() to pass the Function, and the while collecting we are using Collectors.joining() method to convert it to String with delimiter.\nOutput is:\nDoe Jonson Smith ----------------------------------------------\u0026gt;\u0026gt;Above are from peek() Doe|Jonson|Smith 10. Group by Privileges #We have to group the Users on the Privileges, such that it returns Map\u0026lt;Privilege, List\u0026gt;. This is a bit tricky.\nLets the check the Junit for this scenario: #@Test void groupByPrivileges() { User user1 = new User(1L, \u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 26, Collections.singletonList(Privilege.UPDATE)); User user2 = new User(2L, \u0026#34;Greg\u0026#34;, \u0026#34;Jonson\u0026#34;, 30, Arrays.asList( Privilege.UPDATE, Privilege.CREATE, Privilege.DELETE )); User user3 = new User(3L, \u0026#34;Alex\u0026#34;, \u0026#34;Smith\u0026#34;, 13, Collections.singletonList(Privilege.DELETE)); Map\u0026lt;Privilege, List\u0026lt;User\u0026gt;\u0026gt; privilegesMap = userService .groupByPrivileges( new ArrayList\u0026lt;\u0026gt;(Arrays.asList(user1, user2, user3) ) ); Assertions.assertArrayEquals( new int[]{0, 1, 2, 2}, new int[]{ privilegesMap.containsKey(Privilege.READ) ? privilegesMap.get(Privilege.READ).size() : 0, privilegesMap.containsKey(Privilege.CREATE) ? privilegesMap.get(Privilege.CREATE).size() : 0, privilegesMap.containsKey(Privilege.UPDATE) ? privilegesMap.get(Privilege.UPDATE).size() : 0, privilegesMap.containsKey(Privilege.DELETE) ? privilegesMap.get(Privilege.DELETE).size() : 0 } ); } Here, we have 3 Users, which has different Privilege, and we are asserting on Map elements to return the correct size of the users for each Privilege.\nArrays.asList() always returns a fixed sized list. If you try to change the size by adding or removing, it will throw UnsupportedOperationException.\nTo get a variable sized list from Arrays.asList(), have\nnew ArrayList\u0026lt;\u0026gt;(Arrays.asList()) Lets check the logic for this: #This logic is from Stackoverflow* post*.\n@Override public Map\u0026lt;Privilege, List\u0026lt;User\u0026gt;\u0026gt; groupByPrivileges(List\u0026lt;User\u0026gt; users) { // Map\u0026lt;Privilege, List\u0026lt;User\u0026gt;\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); // for(User user: users){ // for(Privilege p: user.getPrivileges()){ // if(map.containsKey(p)) { // List\u0026lt;User\u0026gt; tmp = map.get(p); // tmp.add(user); // map.put(p, tmp); // } else { // map.put(p, new ArrayList\u0026lt;\u0026gt;(Arrays.asList(user))); // } // } // } // return map; return users.stream() .flatMap( user -\u0026gt; user.getPrivileges().stream() .map(privilege -\u0026gt; new AbstractMap.SimpleEntry\u0026lt;\u0026gt;(privilege, user)) ) .peek(System.out::println) .collect( Collectors.groupingBy( Map.Entry::getKey, Collectors.mapping( Map.Entry::getValue, Collectors.toList() ) ) ); } If you notice the commented piece of code, it is the old way of how you would approach the same problem. But with lambdas, and functional programming, we are using the flatMap() to convert the List\u0026lt;List\u0026gt; to create a AbstractMap.SimpleEntry() of Privilege object and User object.\nAbstractMap provides skeletal implementation of the *Map *Interface, to minimize the effort required to implement this interface. To return an *Unmodifiable *map, don\u0026rsquo;t implement the *entrySet() *method. On add or remove, throw UnsupportedOperationException. Do vice-versa to implement *Modifiable *Map.\nAfter creating a map of Privilege and list of user, we have to group by the map\u0026rsquo;s key which is Privilege class based on map\u0026rsquo;s value\u0026rsquo;s size count.\nIts a bit difficult to think this implementation way, which makes it tricky comparing the ask is only to group by Privilege.\nReferences # https://github.com/vbrenister/java8-practice - I have used the problem set from this Github profile. Do check it out. https://stackoverflow.com/a/55145947 - this is for the 10th scenario. This will conclude the list of different scenarios which should help us in learning these implementation ways using lambdas and functional programming. Its always good to try before appearing for an interview, as these have some common ways which are frequently asked in interviews.\nThere will be other ways to do these problem set. Please do comment out if you find any.\nHope this will help you in your learning process. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/day-to-day-lambda-functional-programming-java-8/","section":"Posts","summary":"\u003cp\u003eIf you have seen lately, everyone has shifted to Java 8 and is more or less trying to move from lengthy code blocks to few lines/precise use of Lambda and Functional Programming introduced in Java 8. So, we will talk about all day to day operations that can be converted to use lambda or functional programming and make our code precise and simple.\u003c/p\u003e\n\u003cp\u003eThis post will be a bit lengthy where we will write \u003cem\u003eJunit5\u003c/em\u003e tests and the code for every scenario. It will be interesting to know, as these days, a lot of companies are focusing on candidates to know the use of lambda expressions at least.\u003c/p\u003e","title":"Day to Day Lambda \u0026 Functional Programming in Java 8"},{"content":"In this post, we will build a simple maven project which will create two jar files, which can be executed using below command\njava -jar sample.jar \u0026lt;arguments space separated\u0026gt; In maven, there is plugin called *maven-assembly-plugin, *which will assemble the dependencies and package it into a jar file, provided packaging as jar is given in the maven pom.xml file.\nSo, first and foremost thing is to have packaging given as jar like this\n\u0026lt;groupId\u0026gt;com\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mavenManifestJarDep\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; Now in the build section, add the maven-assembly-plugin like this\n\u0026lt;plugin\u0026gt; \u0026lt;artifactId\u0026gt;maven-assembly-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;single\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;archive\u0026gt; \u0026lt;manifest\u0026gt; \u0026lt;addClasspath\u0026gt;true\u0026lt;/addClasspath\u0026gt; \u0026lt;classpathPrefix\u0026gt;lib/\u0026lt;/classpathPrefix\u0026gt; \u0026lt;mainClass\u0026gt;com.Main\u0026lt;/mainClass\u0026gt; \u0026lt;/manifest\u0026gt; \u0026lt;/archive\u0026gt; \u0026lt;descriptorRefs\u0026gt; \u0026lt;descriptorRef\u0026gt;jar-with-dependencies\u0026lt;/descriptorRef\u0026gt; \u0026lt;/descriptorRefs\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; This will create a manifest file in your jar file with the Main class given as com.Main and will create a new jar file with jar-with-dependencies as suffix. This new jar will have more size as compared to the normal jar file, because if your project happens to have any dependencies, it will be included into this jar file.\nNow you can run the\njava -jar mavenManifestJarDep-1.0-SNAPSHOT.jar \u0026lt;arguments space separated\u0026gt; You can refer this on Github Link for the sample pom.xml file and the target directory for the jar files and their sizes.\nmavenMainfestJarDep-1.0-SNAPSHOT.jar, \u0026amp; mavenMainfestJarDep-1.0-SNAPSHOT-jar-with-dependencies.jar Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/maven-manifest-file-and-jar-with-dependencies/","section":"Posts","summary":"\u003cp\u003eIn this post, we will build a simple maven project which will create two jar files, which can be executed using below command\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ejava \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ejar sample.\u003cspan style=\"color:#a6e22e\"\u003ejar\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003earguments space separated\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn maven, there is plugin called *maven-assembly-plugin, *which will assemble the dependencies and package it into a jar file, provided packaging as jar is given in the maven pom.xml file.\u003c/p\u003e\n\u003cp\u003eSo, first and foremost thing is to have packaging given as jar like this\u003c/p\u003e","title":"Maven - Manifest file and Jar with Dependencies"},{"content":"This post will be to resolve a particular error I encountered while reading the csv files for one of my task. This csv file can contain non-English characters like Chinese or Japanese and hence we need to use the correct encoding format to read the file.\nLets say the csv file contains following:\n\u0026#34;Title\u0026#34;,\u0026#34;Id\u0026#34; \u0026#34;2018-适用于 Windows Server 2016 的 05 累积更新，适合基于 x64 的系统 (KB4103723)\u0026#34;,\u0026#34;e6c7b5bd04ec\u0026#34; \u0026#34;2021-适用于 Windows Server 2016 的 09 服务堆栈更新，适合基于 x64 的系统 (KB5005698)\u0026#34;,\u0026#34;840e7e93b835\u0026#34; \u0026#34;Windows 恶意软件删除工具 x64 - v5.96 (KB890830)\u0026#34;,\u0026#34;cffc4d7bc390\u0026#34; As you can see it contains windows update KB name in Chinese characters.\nIn order to read the content of this file, there were a lot of option given which claimed to resolve the issue. One such approach was like this:\nwith open(\u0026#39;kb.csv\u0026#39;, encoding=\u0026#39;utf8\u0026#39;) as f: reader = csv.reader(f, delimiter=\u0026#34;,\u0026#34;) for line in reader: print(\u0026#34;: \u0026#34;.join(line)) Here we are trying to open the csv file with the encoding set as utf-8. But this would result in following error\nUnicodeDecodeError: \u0026#39;utf-8\u0026#39; codec can\u0026#39;t decode byte 0xff in position 0: invalid start byte I tried other approaches where they were using read_csv() method of *Pandas *library, but it was without any luck.\nAnother approaches were to change the encoding format with cp1252 or ISO-8591-1. But this also couldn\u0026rsquo;t solve the issue.\nSolution #At last what worked for me was changing the encoding format to UTF-16 and decoding the result. The code looks like this\nwith open(\u0026#39;get.csv\u0026#39;, \u0026#39;rb\u0026#39;) as f: content = f.read() content = content.decode(\u0026#39;utf-16\u0026#39;) Here the content once read, was decoded to utf-16 which was missing in all other approaches. As the file was read in utf-8 encoded way, we were not decoding it to get the correct output in other approaches.\nHope this helps others also. Please do suggest more content topics of your choice and share your feedback. Also subscribe and appreciate the blog if you like it.\n","date":"October 27, 2026","permalink":"https://dynamicallyblunttech.com/posts/reading-csv-file-in-python-with-encoding/","section":"Posts","summary":"\u003cp\u003eThis post will be to resolve a particular error I encountered while reading the csv files for one of my task. This csv file can contain \u003cem\u003enon-English\u003c/em\u003e characters like Chinese or Japanese and hence we need to use the correct encoding format to read the file.\u003c/p\u003e\n\u003cp\u003eLets say the csv file contains following:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Title\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Id\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2018-适用于 Windows Server 2016 的 05 累积更新，适合基于 x64 的系统 (KB4103723)\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;e6c7b5bd04ec\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2021-适用于 Windows Server 2016 的 09 服务堆栈更新，适合基于 x64 的系统 (KB5005698)\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;840e7e93b835\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Windows 恶意软件删除工具 x64 - v5.96 (KB890830)\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;cffc4d7bc390\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs you can see it contains windows update KB name in Chinese characters.\u003c/p\u003e","title":"Reading CSV file in Python with Encoding"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/categories/redis/","section":"Categories","summary":"","title":"Redis"},{"content":"","date":null,"permalink":"https://dynamicallyblunttech.com/tags/redis/","section":"Tags","summary":"","title":"Redis"}]