<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Raja Nagendra Kumar, Code Doctor/Innovator on Medium]]></title>
        <description><![CDATA[Stories by Raja Nagendra Kumar, Code Doctor/Innovator on Medium]]></description>
        <link>https://medium.com/@nagendra.raja?source=rss-efd6e4a8f998------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*hrid9JtnGQFDBWbW_MeIAQ.jpeg</url>
            <title>Stories by Raja Nagendra Kumar, Code Doctor/Innovator on Medium</title>
            <link>https://medium.com/@nagendra.raja?source=rss-efd6e4a8f998------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 06 Jun 2026 10:39:09 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@nagendra.raja/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[️ AsciiDoc: The Superior Choice for Software Engineering Documentation]]></title>
            <link>https://medium.com/@nagendra.raja/%EF%B8%8F-asciidoc-the-superior-choice-for-software-engineering-documentation-ccc6f5b554db?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/ccc6f5b554db</guid>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Fri, 01 Aug 2025 08:06:31 GMT</pubDate>
            <atom:updated>2025-08-01T08:06:31.109Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>“Documentation is code.”<em><br> Just like the applications we build, documentation must be </em><strong><em>clear</em></strong><em>, </em><strong><em>maintainable</em></strong><em>, and </em><strong><em>scalable</em></strong><em> — especially in collaborative, fast-paced engineering environments.</em></blockquote><p><strong>Code Doctorship</strong> recommends version-controlling <strong>all relevant documentation directly within your code repositories</strong> — rather than isolating it in a separate CMS.</p><p>Why?</p><ul><li>📂 Docs live alongside the code they describe</li><li>⚡ Engineers can update them quickly, just like code</li><li>🔍 No need to context-switch into another platform</li><li>🔁 Documentation stays in sync with the codebase automatically</li></ul><p>This principle is at the core of scalable engineering teams — and <strong>AsciiDoc</strong> is the best tool to make it happen.</p><p>While many default to Markdown for its simplicity, <strong>AsciiDoc offers a richer, more robust foundation</strong> that evolves with your team, tooling, and documentation needs.</p><h3>✅ The Clean Documentation Imperative</h3><p>Modern engineering teams need documentation that:</p><ul><li>✅ <strong>Stays current</strong> without becoming a maintenance burden</li><li>✅ <strong>Scales consistently</strong> across projects and teams</li><li>✅ <strong>Integrates seamlessly</strong> into development workflows</li><li>✅ <strong>Outputs reliably</strong> across web, PDF, and mobile</li><li>✅ <strong>Maintains semantic structure</strong> — not just visual formatting</li></ul><blockquote><em>Documentation is no longer an afterthought. It’s a first-class citizen in codebases that ship quality software.</em></blockquote><h3>💡 Why AsciiDoc Wins</h3><h3>🔧 Predictable, Standardized Syntax</h3><p>Markdown has multiple incompatible flavors — GitHub, CommonMark, MultiMarkdown — each with subtle differences. This leads to inconsistent rendering across platforms.</p><p><strong>AsciiDoc</strong>, on the other hand, provides one <strong>clean, unified syntax</strong>:</p><pre>= Main Title<br>== Section Header<br>=== Subsection</pre><pre>[source,python]<br>----<br>def clean_code():<br>    return &quot;readable and maintainable&quot;<br>----</pre><p>No guessing. No inconsistencies.</p><h3>🧠 Rich Semantic Markup Without HTML Bloat</h3><p>Need to highlight a tip, warning, or cross-reference?</p><p>AsciiDoc provides <strong>semantic blocks</strong> out of the box:</p><pre>NOTE: This endpoint requires authentication.</pre><pre>TIP: Use batch requests for better performance.</pre><pre>WARNING: This operation cannot be undone.</pre><pre>See &lt;&lt;api-reference&gt;&gt; for complete details.</pre><p>In Markdown? You’re forced into HTML hacks — making your docs fragile and harder to maintain.</p><h3>🖨️ Enterprise-Grade Multi-Format Publishing</h3><p>AsciiDoc, combined with <strong>Asciidoctor</strong>, outputs clean:</p><ul><li>📄 HTML5</li><li>📕 PDF</li><li>📱 EPUB</li><li>📘 DocBook</li></ul><p>All <strong>without format-specific hacks</strong> or third-party converters.<br> Your docs stay consistent and professional across all outputs.</p><h3>🔧 Mature Tooling Ecosystem</h3><p>AsciiDoc isn’t new. It’s <strong>battle-tested</strong> and comes with a thriving ecosystem:</p><ul><li>🧩 <strong>Editor support</strong>: Live preview in VS Code, IntelliJ, Atom, and more</li><li>🔁 <strong>CI/CD integrations</strong>: GitHub Actions, GitLab CI, Jenkins</li><li>🧱 <strong>Modular sites</strong>: Antora enables versioned, multi-project documentation</li><li>📋 <strong>Linting tools</strong>: Vale, alex, and others ensure consistent tone &amp; structure</li></ul><h3>🧪 Format Comparison at a Glance</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UUfqYHOxYQWTXO6Ia-ydPg.png" /></figure><h3>Markdown</h3><ul><li><strong>Pros</strong>: Simple syntax, widely supported, native GitHub rendering.</li><li><strong>Cons</strong>: Lacks standardization (multiple flavors), limited semantic markup, poor scalability for large projects. Markdown often requires HTML embeds for advanced features, breaking portability.</li><li><strong>Use Case</strong>: Best for small projects like READMEs or simple wikis.</li></ul><h3>reStructuredText (reST)</h3><ul><li><strong>Pros</strong>: Strong for Python documentation, good tooling (Sphinx), semantic directives.</li><li><strong>Cons</strong>: Complex syntax (e.g., variable-length underlines for headings), steep learning curve, limited GitHub support.</li><li><strong>Use Case</strong>: Ideal for Python projects or when Sphinx is already in use.</li></ul><h3>Lightweight DITA (LwDITA)</h3><ul><li><strong>Pros</strong>: XML-based, strong for enterprise content reuse, supports complex structures.</li><li><strong>Cons</strong>: Verbose syntax, high learning curve, requires specialized tools (DITA-OT), no GitHub rendering.</li><li><strong>Use Case</strong>: Suitable for large-scale, multi-channel enterprise documentation.</li></ul><h3>Challenges and Mitigations with AsciiDoc</h3><p>While AsciiDoc is powerful, it has some challenges:</p><ul><li><strong>Learning Curve</strong>: Slightly steeper than Markdown due to its richer syntax. <strong>Mitigation</strong>: Provide team training and use cheat sheets or editors with syntax highlighting.</li><li><strong>Tooling Setup</strong>: Requires configuration for CI/CD or static site generation. <strong>Mitigation</strong>: Use pre-built templates (e.g., Antora) or Asciidoctor plugins.</li><li><strong>Community Size</strong>: Smaller than Markdown’s. <strong>Mitigation</strong>: Leverage Asciidoctor’s active community and documentation.</li></ul><h3>Recommended Best Option: AsciiDoc with Asciidoctor and Antora</h3><p>For clean code engineering documentation, <strong>AsciiDoc</strong> paired with <strong>Asciidoctor</strong> and <strong>Antora</strong> is the best choice. Here’s why:</p><ul><li><strong>AsciiDoc</strong> provides a consistent, extensible, and semantically rich markup language that scales from small to large projects.</li><li><strong>Asciidoctor</strong> offers powerful rendering capabilities, real-time previews, and CI/CD integration, aligning with docs-as-code workflows.</li><li><strong>Antora</strong> is a static site generator tailored for AsciiDoc, supporting modular, versioned, and multi-repository documentation — perfect for engineering teams managing complex software projects.</li></ul><h3>Implementation Steps</h3><ol><li><strong>Adopt AsciiDoc</strong>: Store documentation in .adoc files in a Git repository.</li><li><strong>Set Up Asciidoctor</strong>: Use Asciidoctor CLI or plugins for editors like VS Code.</li><li><strong>Use Antora</strong>: Configure Antora for static site generation, enabling versioned documentation.</li><li><strong>Integrate CI/CD</strong>: Automate builds with GitHub Actions or GitLab CI, using Asciidoctor to generate HTML/PDF outputs.</li><li><strong>Lint and Validate</strong>: Use tools like Vale or Asciidoctor extensions for style and syntax checks.</li></ol><h3>🆚 Real-World Example</h3><p>Let’s document an API authentication section:</p><h3>✅ AsciiDoc (Clean and Semantic)</h3><pre>== Authentication<br>:api-version: v2.1</pre><pre>All requests require authentication via API key.</pre><pre>[IMPORTANT]<br>====<br>Store API keys securely. Never commit them to version control.<br>====</pre><pre>=== Quick Start<br>[source,bash]<br>----<br>curl -H &quot;Authorization: Bearer YOUR_KEY&quot; \<br>     <a href="https://api.example.com/{api-version}/users">https://api.example.com/{api-version}/users</a><br>----</pre><pre>For details, see &lt;&lt;security-best-practices&gt;&gt;.</pre><h3>⚠️ Markdown (HTML Workarounds)</h3><pre>## Authentication</pre><pre>All requests require authentication via API key.</pre><pre>&lt;div class=&quot;important&quot;&gt;<br>&lt;strong&gt;Important:&lt;/strong&gt; Store API keys securely. Never commit them to version control.<br>&lt;/div&gt;</pre><pre>### Quick Start<br>```bash<br>curl -H &quot;Authorization: Bearer YOUR_KEY&quot; \<br>     <a href="https://api.example.com/v2.1/users">https://api.example.com/v2.1/users</a></pre><p>For details, see <a href="https://chatgpt.com/c/688c7266-b2c4-8008-8625-04297bbd6c3d#security-best-practices">Security Best Practices</a>.</p><pre>**AsciiDoc wins** with better structure, semantic roles, and maintainability — especially at scale.</pre><pre>---</pre><pre>## 🛠️ Recommended Toolchain for Engineering Teams</pre><pre>| Purpose                          | Tool                  |<br>|----------------------------------|------------------------|<br>| Authoring                       | `.adoc` files in Git   |<br>| Conversion &amp; Preview            | [Asciidoctor](https://asciidoctor.org) |<br>| Multi-project Docs              | [Antora](https://antora.org) |<br>| Linting &amp; Style Checking        | [Vale](https://vale.sh), [alex](https://github.com/get-alex/alex) |<br>| CI/CD Publishing                | GitHub Actions, GitLab CI |</pre><pre>This stack gives your team a **professional documentation pipeline** — just like your code.</pre><pre>---</pre><pre>## 🚀 Making the Switch</pre><pre>Start with a small win:  <br>✅ Convert your `README.md` or a single API section to `.adoc`  <br>✅ Install Asciidoctor CLI or VS Code plugin  <br>✅ See the improvements in rendering, structure, and reuse</pre><pre>### 🙋‍♀️ Common Concerns</pre><pre>- **Learning Curve?**  <br>  Easier than it looks. Most devs are productive in a few hours.</pre><pre>- **Tooling?**  <br>  Modern IDEs and CI tools support AsciiDoc out of the box.</pre><pre>- **GitHub?**  <br>  GitHub renders `.adoc` files natively using Asciidoctor.</pre><pre>---</pre><pre>## 🩺 Code Doctors for Legacy Documentation Overhaul</pre><pre>Many engineering teams are sitting on **years of undocumented or Markdown-spaghetti tech debt** — with README files patched over time, fragile HTML hacks, and inconsistent structures across repos.</pre><pre>That’s where the **Code Doctorship** from [ShivohamAI](http://www.shivohamai.com/services) steps in.</pre><pre>### 👨‍⚕️ What the Code Doctors Do:<br>- **Diagnose** outdated or fragmented documentation systems  <br>- **Migrate** legacy Markdown, HTML, or custom formats to AsciiDoc — cleanly and quickly  <br>- **Refactor** doc structures for modularity, reuse, and CI/CD integration  <br>- **Prescribe** best practices for scalable tooling: Antora, Asciidoctor, Vale, GitOps  <br>- **Treat** your documentation as a living system — not a last-minute checklist</pre><pre>### 🧬 Why It Matters:<br>Just like legacy code, **legacy documentation** slows down onboarding, increases errors, and weakens product understanding — especially in regulated or fast-moving environments.</pre><pre>By partnering with Code Doctorship, teams can:<br>- 🧹 Eliminate inconsistent syntax and broken formats  <br>- 🚀 Accelerate product delivery through clearer internal and external documentation  <br>- 🔄 Align documentation updates with code releases  <br>- 🧠 Empower both engineers and stakeholders with searchable, readable knowledge  </pre><pre>---</pre><pre>## 📌 The Bottom Line</pre><pre>**Documentation debt is technical debt.**  <br>Markdown works for small projects. But for teams that care about **scale**, **structure**, and **professional output**, **AsciiDoc is the clear winner**.</pre><pre>It’s not just a markup language.  <br>It’s a mindset:  <br>🧠 *Treating docs like code.*  <br>🧰 *Using the right tools for the job.*  <br>📈 *Scaling with your ambitions.*</pre><pre>---</pre><pre>### 💬 Ready to Upgrade Your Docs?</pre><pre>AsciiDoc is more than markup. It&#39;s your team&#39;s commitment to engineering excellence.</pre><pre>&gt; *Documentation that scales should be as rigorous as the systems it describes.*</pre><pre>—</pre><pre>Need help migrating from Markdown or CMS-based documentation?  <br>👉 **[Let Code Doctorship help.](http://www.shivohamai.com/services)**</pre><h3>Conclusion</h3><p>AsciiDoc outperforms Markdown, reST, and LwDITA for clean code engineering documentation due to its consistent syntax, rich semantic markup, extensibility, and robust tooling. While Markdown is simpler for small projects, it lacks the scalability and portability needed for complex engineering documentation. reST and LwDITA, while powerful, have steeper learning curves and less seamless integration with modern developer workflows. By adopting AsciiDoc with Asciidoctor and Antora, engineering teams can create documentation that is as clean, maintainable, and collaborative as their code, ensuring long-term success in software development.</p><h3>🩺 Code Doctors for Legacy Documentation Overhaul</h3><p>Many engineering teams are sitting on <strong>years of undocumented or Markdown-spaghetti tech debt</strong> — with README files patched over time, fragile HTML hacks, and inconsistent structure across repos.</p><p>That’s where the <strong>Code Doctorship</strong> from ShivohamAI steps in.</p><h3>👨‍⚕️ What the Code Doctors Do:</h3><ul><li><strong>Diagnose</strong> outdated or fragmented documentation systems</li><li><strong>Migrate</strong> legacy Markdown, HTML, or custom formats to AsciiDoc — cleanly and quickly</li><li><strong>Refactor</strong> doc structures for modularity, reuse, and CI/CD integration</li><li><strong>Prescribe</strong> best practices for scalable tooling: Antora, Asciidoctor, Vale, GitOps</li><li><strong>Treat</strong> your documentation as a living system — not a last-minute checklist</li></ul><h3>🧬 Why It Matters:</h3><p>Just like legacy code, <strong>legacy documentation</strong> slows down onboarding, increases errors, and weakens product understanding — especially in regulated or fast-growing environments.</p><p>By partnering with Code Doctorship, teams can:</p><ul><li>🧹 Eliminate inconsistent syntax and broken formats</li><li>🚀 Accelerate product delivery through clearer internal and external documentation</li><li>🔄 Align documentation updates with code releases</li><li>🧠 Empower both engineers and stakeholders with searchable, readable knowledge</li></ul><h3>💡 Your Docs Deserve a Second Opinion</h3><p>You wouldn’t let a 2025-era Java app ship without cleanup.<br> Don’t let your docs lag behind either.</p><p><strong>Reach out to </strong><a href="http://www.shivohamai.com/services"><strong>ShivohamAI</strong> </a>to give your documentation a full check-up and chart a clean path forward — from chaos to clarity.</p><h3>📢 Disclosure</h3><blockquote><em>🧠 </em><strong><em>This article was co-created using AI tools and curated by experts from ShivohamAI.</em></strong><em><br> AI-assisted drafting helped accelerate the writing process, but all technical insights, toolchain choices, and formatting practices are grounded in real-world engineering experience.</em></blockquote><blockquote><em>🔄 </em><strong><em>This document is a living artifact.</em></strong><em><br> It will continue to evolve based on feedback, community input, and advancements in tooling — just like good documentation should.</em></blockquote><p>Have suggestions or spotted something we can improve?<br>We welcome contributions and insights to make this even more useful.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ccc6f5b554db" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mastering Gradle Publishing]]></title>
            <link>https://medium.com/@nagendra.raja/mastering-gradle-publishing-a-journey-through-maven-maven-publish-gradle-plugin-publish-and-1659eee69eac?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/1659eee69eac</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[gradle]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Thu, 31 Jul 2025 07:31:08 GMT</pubDate>
            <atom:updated>2025-07-31T09:45:21.423Z</atom:updated>
            <content:encoded><![CDATA[<h3>A Journey Through maven, maven-publish, gradle-plugin-publish, and Vanniktech</h3><h3>Introduction</h3><p>Publishing artifacts is a critical part of modern software development, whether you’re sharing a library on Maven Central, releasing a Gradle plugin, or deploying to a private repository. Gradle, as a flexible build tool, has evolved its publishing capabilities to meet diverse needs. In this article, I’ll explore four well-known Gradle publishing plugins, tracing their evolution and comparing their strengths, weaknesses, and ideal use cases. These plugins are:</p><ul><li><a href="https://docs.gradle.org/current/userguide/maven_plugin.html"><strong>Maven Plugin</strong></a> (Deprecated): Gradle’s original solution for Maven repository publishing.</li><li><a href="https://docs.gradle.org/current/userguide/publishing_maven.html"><strong>Maven-Publish Plugin</strong></a>: Gradle’s modern, official plugin for Maven-compatible publishing.</li><li><a href="https://plugins.gradle.org/plugin/com.gradle.plugin-publish"><strong>Gradle Plugin Publish Plugin</strong></a>: A specialized plugin for publishing Gradle plugins to the Gradle Plugin Portal.</li><li><a href="https://github.com/vanniktech/gradle-maven-publish-plugin"><strong>Vanniktech Gradle Maven Publish Plugin</strong></a>: A third-party plugin that simplifies publishing to Maven Central and Nexus.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/908/1*SDGR1zfC793djU9YTpAt1Q.png" /></figure><p>Let’s dive into how these plugins have shaped Gradle’s publishing landscape and when to use each.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xZoyTk9YyQdEocsasAwBcg.png" /></figure><h3>1. Maven Plugin (Deprecated)</h3><h4>Overview</h4><p>The maven plugin was Gradle’s original solution for publishing artifacts to Maven repositories or generating Maven-compatible project structures (e.g., POM files). It was designed to bridge Gradle projects with Maven-based ecosystems.</p><h4>Evolution</h4><ul><li><strong>Introduced</strong>: Early Gradle versions (pre-2012) to support Maven repository publishing and project conversion.</li><li><strong>Functionality</strong>: Provided tasks like uploadArchives to publish JARs and generate POM files. It allowed basic POM customization but was limited in scope.</li><li><strong>Limitations</strong>:</li><li>Verbose configuration for repositories and artifacts.</li><li>No support for modern Gradle features like the publishing DSL or Gradle Module Metadata.</li><li>Poor handling of complex projects (e.g., multi-module or non-Java artifacts).</li><li><strong>Deprecation</strong>: Superseded by maven-publish in Gradle 4.8 (2018) due to its inflexibility and lack of support for advanced publishing needs.</li></ul><h4>Strengths</h4><ul><li>Simple for basic Java projects in early Gradle versions.</li><li>Familiar to Maven users transitioning to Gradle.</li></ul><h4>Weaknesses</h4><ul><li>Outdated and unsupported since 2018.</li><li>Limited customization and no support for modern repository requirements (e.g., signing, Gradle Module Metadata).</li><li>Error-prone for complex builds.</li></ul><h4>When to Use</h4><ul><li><strong>Not Recommended</strong>: Only for maintaining legacy projects pre-2018 that haven’t migrated to maven-publish.</li><li><strong>Use Case</strong>: Rarely used today; primarily for old builds needing basic Maven repository publishing.</li></ul><h4>How to Use</h4><pre>pply plugin: &#39;maven&#39;<br><br>uploadArchives {<br>    repositories {<br>        mavenDeployer {<br>            repository(url: &#39;https://my-repo.com&#39;) {<br>                authentication(userName: &#39;user&#39;, password: &#39;pass&#39;)<br>            }<br>            pom.groupId = &#39;org.example&#39;<br>            pom.artifactId = &#39;my-library&#39;<br>            pom.version = &#39;1.0.0&#39;<br>        }<br>    }<br>}</pre><h3>2. Maven-Publish Plugin</h3><h4>Overview</h4><p>The maven-publish plugin is Gradle’s modern, official solution for publishing artifacts to Maven repositories. Introduced as a replacement for the maven plugin, it uses a declarative publishing DSL to define publications and repositories.</p><h4>Evolution</h4><ul><li><strong>Introduced</strong>: Around Gradle 2.x (2013–2014), with significant improvements by Gradle 4.8 (2018).</li><li><strong>Key Features</strong>:</li><li><strong>Publications</strong>: Define MavenPublication objects to specify artifacts (e.g., JARs, sources, Javadoc) and POM metadata.</li><li><strong>Repositories</strong>: Configure MavenArtifactRepository for remote (e.g., Maven Central, Nexus) or local (mavenLocal) publishing.</li><li><strong>POM Customization</strong>: Modify POM files via pom.withXml or direct properties (e.g., groupId, artifactId).</li><li><strong>Task Generation</strong>: Creates tasks like publishPubNamePublicationToRepoNameRepository and aggregate tasks (publish, publishToMavenLocal).</li><li><strong>Component Support</strong>: Publishes components (e.g., components.java) from plugins like java or java-library.</li><li><strong>Gradle Module Metadata</strong>: Since Gradle 6.0 (2019), includes metadata for better dependency resolution in Gradle builds.</li><li><strong>Advancements</strong>:</li><li>Added signing support for Maven Central (via signing plugin).</li><li>Improved integration with plugins like kotlin-multiplatform and com.android.library.</li><li>Enhanced DSL for multi-module projects and custom artifacts.</li><li><strong>Challenges</strong>:</li><li>Requires manual setup for signing, sources, and Javadoc.</li><li>Maven Central publishing involves complex configuration (e.g., staging, credentials).</li><li>Steeper learning curve for beginners.</li></ul><h4>Strengths</h4><ul><li><strong>Flexibility</strong>: Supports custom artifacts, multiple repositories, and detailed POM customization.</li><li><strong>Official Support</strong>: Actively maintained, compatible with modern Gradle features.</li><li><strong>Broad Compatibility</strong>: Works with Java, Kotlin, Android, and multiplatform projects.</li></ul><h4>Weaknesses</h4><ul><li><strong>Complexity</strong>: Requires understanding of Gradle’s publishing model.</li><li><strong>Manual Overhead</strong>: Maven Central publishing needs additional setup (e.g., signing, staging).</li><li><strong>Verbose for Simple Cases</strong>: Basic publishing requires more code than specialized plugins.</li></ul><h4>When to Use</h4><ul><li><strong>Use Case</strong>: Projects needing fine-grained control over publishing, such as custom artifacts, multiple repositories, or non-Maven Central targets.</li><li><strong>Examples</strong>:</li><li>Publishing a multi-module Java project to a private Nexus repository.</li><li>Customizing POM files for specific dependency requirements.</li><li>Publishing to mavenLocal for local testing.</li><li><strong>How to Use</strong>:</li></ul><pre>plugins {<br>    id &#39;maven-publish&#39;<br>}<br><br>publishing {<br>    publications {<br>        maven(MavenPublication) {<br>            groupId = &#39;org.example&#39;<br>            artifactId = &#39;my-library&#39;<br>            version = &#39;1.0.0&#39;<br>            from components.java<br>        }<br>    }<br>    repositories {<br>        maven {<br>            url &#39;https://my-repo.com&#39;<br>            credentials {<br>                username &#39;user&#39;<br>                password &#39;pass&#39;<br>            }<br>        }<br>    }<br>}</pre><p>Run gradle publish or gradle publishToMavenLocal.</p><h3>3. Gradle Plugin Publish Plugin (gradle-plugin-publish)</h3><h4>Overview</h4><p>The gradle-plugin-publish plugin (likely what you meant by publish-plugin) is designed specifically for publishing Gradle plugins to the Gradle Plugin Portal. It builds on maven-publish to simplify the process for plugin authors.</p><h4>Evolution</h4><ul><li><strong>Introduced</strong>: Around 2016 by Gradle, Inc., to streamline publishing to the Gradle Plugin Portal.</li><li><strong>Purpose</strong>: Simplifies publishing Gradle plugins by automating POM generation, plugin metadata, and portal-specific requirements.</li><li><strong>Key Features</strong>:</li><li><strong>Plugin Metadata</strong>: Configures plugin descriptors (e.g., id, implementation-class) via a gradlePlugin block.</li><li><strong>Automatic POM</strong>: Generates a POM file tailored for the Gradle Plugin Portal, including plugin-specific metadata.</li><li><strong>Single Repository</strong>: Targets the Gradle Plugin Portal (https://plugins.gradle.org/m2) by default.</li><li><strong>Tasks</strong>: Provides publishPlugins to publish to the portal and publishToMavenLocal for testing.</li><li><strong>Integration</strong>: Works with the java-gradle-plugin plugin to define plugins and their implementation.</li><li><strong>Advancements</strong>:</li><li>Simplified plugin ID registration and authentication via API keys.</li><li>Improved error messages and validation for portal requirements.</li><li>Support for modern Gradle versions and plugin types (e.g., Kotlin-based plugins).</li><li><strong>Challenges</strong>:</li><li>Limited to Gradle Plugin Portal; not suitable for general Maven repositories.</li><li>Requires a registered plugin ID and API keys from the portal.</li><li>Less flexible than maven-publish for custom publishing scenarios.</li></ul><h4>Strengths</h4><ul><li><strong>Simplicity</strong>: Streamlines publishing Gradle plugins with minimal configuration.</li><li><strong>Portal-Specific</strong>: Tailored for the Gradle Plugin Portal’s requirements.</li><li><strong>Integration</strong>: Works seamlessly with java-gradle-plugin for plugin development.</li></ul><h4>Weaknesses</h4><ul><li><strong>Narrow Scope</strong>: Only for Gradle Plugin Portal, not general Maven repositories.</li><li><strong>Dependency on maven-publish</strong>: Inherits some complexity for edge cases.</li><li><strong>Setup Overhead</strong>: Requires portal registration and API key management.</li></ul><h4>When to Use</h4><ul><li><strong>Use Case</strong>: Publishing Gradle plugins to the Gradle Plugin Portal.</li><li><strong>Examples</strong>:</li><li>Releasing a custom Gradle plugin for build automation.</li><li>Publishing a Kotlin-based Gradle plugin to the portal.</li></ul><p><strong>How to Use</strong>:</p><pre>plugins {<br>    id &#39;java-gradle-plugin&#39;<br>    id &#39;com.gradle.plugin-publish&#39; version &#39;1.2.1&#39;<br>}<br><br>gradlePlugin {<br>    plugins {<br>        myPlugin {<br>            id = &#39;org.example.my-plugin&#39;<br>            implementationClass = &#39;org.example.MyPlugin&#39;<br>            displayName = &#39;My Gradle Plugin&#39;<br>            description = &#39;A plugin for custom build tasks&#39;<br>            tags = [&#39;build&#39;, &#39;automation&#39;]<br>        }<br>    }<br>}<br><br>pluginBundle {<br>    website = &#39;https://example.com&#39;<br>    vcsUrl = &#39;https://github.com/example/my-plugin&#39;<br>}</pre><p>Add credentials to gradle.properties:</p><p>properties</p><pre>gradle.publish.key=your-key<br>gradle.publish.secret=your-secret</pre><p>Run gradle publishPlugins to publish.</p><h3>4. Vanniktech Gradle Maven Publish Plugin</h3><h4>Overview</h4><p>The com.vanniktech.maven.publish plugin is a third-party plugin that enhances maven-publish to simplify publishing to Maven Central and other Nexus repositories, particularly for Java, Kotlin, Android, and multiplatform projects.</p><h4>Evolution</h4><p><strong>Origin</strong>: Evolved from Chris Banes’ plugin (2018) to address maven-publish limitations for Android and Kotlin.</p><p><strong>Milestones</strong>:</p><ul><li><strong>2018–2020</strong>: Added support for Android, Kotlin, and automatic sources/Javadoc generation.</li><li><strong>2020–2022</strong>: Extended to Kotlin Multiplatform and Gradle plugins, with CI-friendly signing.</li><li><strong>2023–2025</strong>: Adapted to Maven Central’s Central Portal (post-OSSRH deprecation in June 2025), added automatic releasing, and improved Javadoc handling.</li></ul><p><strong>Key Features</strong>:</p><ul><li><strong>Maven Central Automation</strong>: Simplifies signing, staging, and releasing with options like publishToMavenCentral() and automaticRelease.</li><li><strong>In-Memory Signing</strong>: Uses environment variables for GPG keys, ideal for CI/CD.</li><li><strong>Cross-Platform</strong>: Supports Java, Kotlin, Android, Kotlin Multiplatform, and Gradle plugins.</li><li><strong>Base Variant</strong>: Offers com.vanniktech.maven.publish.base for manual configuration.</li><li><strong>Configuration</strong>: Uses gradle.properties for credentials and metadata, reducing build script complexity.</li></ul><h4>Strengths</h4><ul><li><strong>Automation</strong>: Handles Maven Central requirements (signing, staging, releasing) with minimal setup.</li><li><strong>Broad Support</strong>: Works with Java, Kotlin, Android, multiplatform, and Gradle plugins.</li><li><strong>CI-Friendly</strong>: In-memory signing and property-based configuration simplify pipelines.</li><li><strong>Active Maintenance</strong>: Regularly updated for Gradle and Maven Central changes.</li></ul><h4>Weaknesses</h4><ul><li><strong>Maven-Centric</strong>: Less suitable for non-Maven repositories.</li><li><strong>Base Plugin Complexity</strong>: Manual configuration in base variant can be complex.</li><li><strong>Dependency on maven-publish</strong>: Inherits some underlying complexities.</li></ul><h4>When to Use</h4><ul><li><strong>Use Case</strong>: Publishing to Maven Central or Nexus, especially for Java, Kotlin, Android, or multiplatform projects with automated workflows.</li><li><strong>Examples</strong>:</li><li>Releasing an Android library to Maven Central with sources and Javadoc.</li><li>Automating CI/CD for a Kotlin Multiplatform library.</li></ul><p><strong>How to Use</strong>:</p><pre>plugins {<br>    id &#39;com.vanniktech.maven.publish&#39; version &#39;0.32.0&#39;<br>}<br><br>mavenPublishing {<br>    publishToMavenCentral()<br>    signAllPublications()<br>}</pre><p>In gradle.properties:</p><pre>mavenCentralUsername=username<br>mavenCentralPassword=password<br>signing.keyId=12345678<br>signing.password=some_password<br>signing.secretKeyRingFile=/path/to/secring.gpg</pre><p>Run gradle publishToMavenCentral or enable automaticRelease = true.</p><h3>Comparison and Decision Matrix</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uihOKWn8VZDSV9YIMv4SGg.png" /></figure><h4>When to Choose Each</h4><ol><li><strong>Maven Plugin</strong>:</li></ol><ul><li><strong>When</strong>: Only for legacy projects pre-2018 that haven’t migrated.</li><li><strong>Why</strong>: Deprecated, lacks modern features, and is unsupported.</li></ul><p><strong>2. Maven-Publish Plugin</strong>:</p><ul><li><strong>When</strong>: You need full control over publishing (e.g., custom artifacts, multiple repositories) or target non-Maven Central repositories.</li><li><strong>Why</strong>: Flexible, officially supported, but requires more configuration.</li><li><strong>Example</strong>: Publishing to a private Nexus repository with custom POM metadata.</li></ul><p><strong>3. Gradle Plugin Publish Plugin</strong>:</p><ul><li><strong>When</strong>: You’re publishing a Gradle plugin to the Gradle Plugin Portal.</li><li><strong>Why</strong>: Tailored for the portal, simplifies plugin metadata and publishing.</li><li><strong>Example</strong>: Releasing a custom Gradle plugin for build automation.</li></ul><p><strong>4. Vanniktech Plugin</strong>:</p><ul><li><strong>When</strong>: Targeting Maven Central or Nexus with minimal setup, especially for Java, Kotlin, Android, or multiplatform projects.</li><li><strong>Why</strong>: Automates Maven Central workflows, supports modern project types, and is CI-friendly.</li><li><strong>Example</strong>: Publishing an Android library to Maven Central with automatic signing</li></ul><h3>Recommendations</h3><ul><li><strong>New Projects</strong>: Use gradle-plugin-publish for Gradle plugins targeting the Gradle Plugin Portal, or Vanniktech’s plugin for Maven Central/Nexus publishing. Use maven-publish for custom or non-standard repository needs.</li><li><strong>Legacy Projects</strong>: Migrate from maven plugin to maven-publish or Vanniktech for modern features.</li><li><strong>Testing</strong>: Use publishToMavenLocal (available in maven-publish, gradle-plugin-publish, and Vanniktech) for local testing.</li><li><strong>CI/CD</strong>: Leverage Vanniktech’s in-memory signing and gradle.properties for secure, automated pipelines.</li><li><strong>Maven Central</strong>: Prefer Vanniktech for its automation, but use maven-publish if you need custom POM tweaks or non-standard artifacts.</li></ul><h3>🛠️ Final Notes</h3><p>This article is intended to <strong>highlight key Gradle publishing options</strong> and share insights on choosing the <strong>best plugin to start with</strong>.<br> Please note: <em>some of the script samples were generated using ChatGPT and may require validation before production use.</em></p><h3>🩺 Need Help Migrating Legacy Gradle Publishing?</h3><p>If you’re using older plugins like maven or facing complexity with your current publishing setup, we recommend connecting with the experts at <strong>Code Doctorship</strong>.</p><p>They specialize in:</p><ul><li>🚑 <strong>Migration from deprecated plugins to modern publishing flows</strong></li><li>🔧 Refactoring legacy Gradle scripts for clarity and maintainability</li><li>🔍 Ensuring smooth publication to Maven Central, GitHub Packages, or the Gradle Plugin Portal</li><li>⚙️ Automating release pipelines and CI/CD publishing tasks</li></ul><p>➡️ <strong>Learn more at</strong> <a href="http://www.shivohamai.com/services">shivohamai.com/services</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1659eee69eac" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Versioning on Autopilot: Empowering Instant Integration for Agile Teams]]></title>
            <link>https://medium.com/@nagendra.raja/versioning-on-autopilot-empowering-instant-integration-for-agile-teams-f07a96c50e18?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/f07a96c50e18</guid>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Wed, 30 Jul 2025 16:31:10 GMT</pubDate>
            <atom:updated>2025-07-30T16:35:01.234Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>In this AI-generated code era, manual versioning is the new #techdebt — <strong>Every Scalable Engineering Leader Ever</strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dGbbZ_a3i2pn54fe" /></figure><p>Software development environment, multiple teams often collaborate on various projects, making version control a critical aspect of the development lifecycle. Auto-generated version control can significantly enhance productivity and ensure seamless integration across teams. This article explores how to implement auto-generated version control using the GrabVer library and demonstrates its importance through practical examples.</p><h4>The Importance of Auto-Versioning in Corporate Software Development</h4><ol><li><em>Enhancing Collaboration</em></li></ol><p>Auto-versioning ensures that all team members are on the same page, reducing the risk of conflicts and miscommunications. It allows for instant integration, which is crucial in large organizations where multiple teams depend on each other’s work.</p><p><em>2. Reducing Technical Debt</em></p><p>By automating the versioning process, teams can focus on delivering high-quality code rather than managing version numbers manually. This reduces technical debt and ensures that the codebase remains clean and maintainable.</p><p><em>3. Ensuring Consistency</em></p><p>Auto-versioning helps maintain consistency across different projects and teams. It ensures that all dependencies are up-to-date and that any changes are immediately reflected in the dependent projects.</p><p>Where teams produce and consume each other’s libraries, <strong>auto-generated versioning</strong> ensures <strong>instant integration</strong> without chaos. This article walks you through how to:</p><ul><li>Apply <strong>semantic versioning automatically</strong> using <a href="https://github.com/davideas/GrabVer">GrabVer</a></li><li>Enable <strong>immediate feedback loops</strong> between producers and consumers</li><li>Use <strong>latest.integration</strong> effectively</li><li>Apply all this in real-world projects like:</li></ul><p><a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/AssertsCounter">AssertsCounter</a></p><p><a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/systemexit/junit5exit">JUnit5Exit</a></p><p>By the end, you’ll not only love auto-versioning but also see why it’s a cornerstone of collaboration and agility in large engineering orgs.</p><h3>🚨 The Problem: Manual Versioning Slows You Down and bound to errors at Integration time</h3><p>Imagine a world where:</p><ul><li>Team A builds a reusable test utility.</li><li>Team B consumes it and starts development.</li><li>One day, Team A pushes a bug fix.</li></ul><p>But Team B never gets the fix unless someone <strong>bumps the version manually</strong>, publishes it, and updates the consumer side.</p><p>Now scale this across <strong>10s of teams</strong>, multiple services, parallel timelines, and non-synced releases. Welcome to <strong>Versioning, Merge &amp; Integration Hell</strong>!</p><h3>✅ The Solution: Auto-Versioning with GrabVer</h3><p><a href="https://github.com/davideas/GrabVer">GrabVer</a> is a Gradle plugin that:</p><ul><li>Derives versions from Git history (no manual bumping)</li><li>Supports semver with branches and tags</li><li>Works seamlessly with Maven Central, GitHub Packages, JitPack, etc.</li></ul><p>It essentially transforms your build into a <strong>version-aware, self-publishing artifact factory</strong>.</p><h3>🎯 Goals of This Article</h3><ul><li>Show you <strong>how</strong> to integrate auto-versioning with GrabVer</li><li>Demonstrate <strong>real Gradle Kotlin DSL examples</strong></li><li>Link <strong>two real-world projects</strong> to show producer-consumer integration</li><li>Highlight <strong>CI/CD automation with a single Gradle command</strong></li></ul><h3>Project Setup: Meet the Producer</h3><p>Our producer project is <a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/AssertsCounter">AssertsCounter</a>.</p><p>It’s a Gradle Plugin that counts JUnit 5 assertions and logs results — a helpful tool for test quality metrics.</p><h3>Step 1: Add GrabVer to build.gradle.kts</h3><pre>plugins {<br>    id(&quot;java&quot;)<br>    id(&quot;eu.davidea.grabver&quot;) version &quot;2.0.3&quot;<br>}<br><br>versioning {<br>    preRelease = &quot;SNAPSHOT&quot;<br>    saveOn = &quot;publish&quot; // Save version to file only when publishing<br>}</pre><h3>Step 2: Configure the Group &amp; Publishing</h3><pre>group = &quot;com.shivohamai.cc&quot;<br>version = versioning.name  // GrabVer generates version!</pre><pre>publishing {<br>    publications {<br>        create&lt;MavenPublication&gt;(&quot;mavenJava&quot;) {<br>            from(components[&quot;java&quot;])<br>            versionMapping {<br>                usage(&quot;java-api&quot;) {<br>                    fromResolutionOf(&quot;runtimeClasspath&quot;)<br>                }<br>            }<br>        }<br>    }<br>    repositories {<br>        maven {<br>            name = &quot;GitHubPackages&quot;<br>            url = uri(&quot;https://maven.pkg.github.com/nagkumar/java&quot;)<br>            credentials {<br>                username = System.getenv(&quot;GITHUB_ACTOR&quot;)<br>                password = System.getenv(&quot;GITHUB_TOKEN&quot;)<br>            }<br>        }<br>    }<br>}</pre><h3>Step 3: Publish with Version Awareness</h3><p>Run this on every change to auto-publish:</p><pre>./gradlew clean build test publish</pre><blockquote>This simple config lets you:</blockquote><ul><li>Auto-bump versions based on commit history or CI context.</li><li>Tag releases automatically.</li><li>Generate pre-releases when testing.</li></ul><h3>Result?</h3><p>Instead of maintaining this manually:</p><pre>version = &quot;1.4.2-alpha-1&quot;</pre><p>GrabVer can auto-generate:</p><pre>version = &quot;1.4.3-SNAPSHOT&quot; // on CI, based on your git status</pre><blockquote><em>💡 GrabVer detects the current version and assigns a unique semver like </em><em>1.0.0-beta+001.</em></blockquote><h3>🔁 Enter the Consumer: JUnit5Exit</h3><p>Your <a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/systemexit/junit5exit">JUnit5Exit</a> project consumes the published AssertsCounter.</p><p>Here’s how you connect it:</p><h3>Add the plugin:</h3><pre>plugins {<br>    id(&quot;asserts-counter-plugin&quot;) version &quot;latest.integration&quot;<br>}</pre><blockquote><em>latest.integration tells Gradle: &quot;Get me the most recently published version—even if it&#39;s a snapshot.&quot;</em></blockquote><h3>Fetch Updates Immediately</h3><p>To ensure you’re consuming the very latest version, run:</p><pre>gradle --rerun-tasks --refresh-dependencies --no-parallel --stacktrace</pre><blockquote><em>⚠️ Without </em><em>--refresh-dependencies, you might still be using a cached older version!</em></blockquote><h3>🧠 Why It Matters in Corporate Engineering</h3><p>In a large engineering organization with <strong>tens or hundreds of developers</strong>, the dynamics are different.</p><h3>Traditional Way (aka Pain):</h3><ul><li>Manual versioning</li><li>Missed patches</li><li>Merge conflicts over build.gradle.kts</li><li>CI build flakiness</li><li>QA asking “which version do I test?”</li></ul><h3>Auto-Versioning Way (aka Sanity):</h3><ul><li>Teams produce and publish as they go</li><li>Other teams always consume the latest published version</li><li>No need to “agree” on version numbers</li><li>Git becomes the version source of truth</li></ul><h3>🧪 Real World Dev Workflow</h3><h3>Dev pushes to main</h3><p>→ GrabVer computes version 1.2.0+sha1234</p><h3>CI runs:</h3><pre>gradle clean build test publish</pre><p>→ Artifact published to GitHub Packages or Maven Central</p><h3>Another team runs:</h3><pre>gradle --refresh-dependencies</pre><p>→ Instantly gets the updated lib</p><h3>🖼️ Image: Auto-Versioning Lifecycle</h3><p>Imagine a DevOps flow diagram that shows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/791/1*5QZDvr11Mb0WSgpQ-YpMXQ.png" /></figure><h3>💥 Bonus: Zero Merge Conflicts in build.gradle.kts</h3><p>If every team uses latest.integration, then no one needs to argue over:</p><pre>- testImplementation(&quot;x:lib:1.1.2&quot;)<br>+ testImplementation(&quot;x:lib:1.1.3&quot;)</pre><p>Everyone is always aligned with the <strong>published truth</strong>.</p><h3>🔚 Final Words</h3><p>Auto-generated version control is a critical aspect of modern software development, especially in large organizations where multiple teams collaborate on various projects. By implementing auto-versioning using the GrabVer library, you can enhance collaboration, reduce technical debt, and ensure consistency across your projects. If you’re struggling with technical debt or legacy code, consider reaching out to Code Doctorship services for expert assistance.</p><p>Auto-generated versioning with GrabVer is not just a “nice to have.” It’s <strong>how modern teams integrate seamlessly</strong>, avoid drift, and ship confidently.</p><p>By connecting producer-consumer workflows with latest.integrationYou ensure:</p><ul><li>Faster feedback</li><li>Sane dependency graphs</li><li>Clean release pipelines</li></ul><p>And most importantly — <strong>peace of mind for everyone from engineers to execs</strong>.</p><h3>👨‍⚕️ Code Doctorship to treat Your Product CODE Mess</h3><blockquote><em>If your org has devs buried under excuses like:</em></blockquote><ul><li>“We can’t release; it’s not tested.”</li><li>“No one bumped the version.”</li><li>“We have too much tech debt.”</li><li>“Legacy code doesn’t allow this.”</li></ul><p>Then it’s time for <strong>Code Doctorship</strong>.</p><p>Visit 👉 <a href="http://www.shivohamai.com/services">www.shivohamai.com/services</a><br> We provide:</p><ul><li>Instant assessment of your versioning and publishing processes</li><li>Cleanup of untested legacy libs</li><li>Setup of CI/CD workflows with auto-versioning</li><li>Teaching teams how to be <strong>producers</strong>, not just consumers</li></ul><h3>💬 Let’s Talk</h3><p>If this article resonates with your org’s challenges,<br> 💌 connect with me at <a href="http://www.shivohamai.com/services">shivohamai.com/services</a><br> We fix legacy messes. We stop the excuse trains. We empower engineers.</p><h3>📎 Related Links</h3><ul><li><a href="https://github.com/davideas/GrabVer">GrabVer on GitHub</a></li><li><a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/AssertsCounter">AssertsCounter</a></li><li><a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/systemexit/junit5exit">JUnit5Exit</a></li><li><a href="http://www.shivohamai.com/services">ShivohamAI Services</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f07a96c50e18" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Counting Asserts in JUnit Tests: What Started as a Script Became a Plugin]]></title>
            <link>https://medium.com/@nagendra.raja/counting-asserts-in-junit-tests-what-started-as-a-script-became-a-plugin-64ac682e92b0?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/64ac682e92b0</guid>
            <category><![CDATA[gradle-plugin]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[junit]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Mon, 28 Jul 2025 10:09:51 GMT</pubDate>
            <atom:updated>2025-07-28T10:21:16.851Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>“Too much scripting is like a long traffic jam. Plugins are the carpool lanes of Gradle.”</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XIVQfWRWrGUrRHPS" /></figure><h3>☕ Too Much Gradle Scripts, Not Enough Time</h3><p>Let’s admit it — Gradle build scripts have a reputation. Some love their declarative freedom; others feel like they’ve entered an Indiana Jones booby-trapped cave every time they open build.gradle.kts.</p><p>Now picture this: You’ve built a JVM agent — let’s call it acagent — that counts all assert* calls in unit tests (you know, to see if your tests are really verifying the business objects&#39; state). It’s useful, lightweight, and elegant…</p><p>Until you try to <strong>wire it into every test task</strong> of every project using Gradle.</p><p>Your nice little agent turns into a scripting nightmare.</p><p>I lived this. And that’s why I built a plugin to fix it — and then published both the <strong>agent</strong> and <strong>plugin</strong> to Gradle Packages so you don’t have to touch a line of test task of GradlejvmArgs(&quot;-javaagent:...&quot;) again.</p><p>This post walks through:</p><ul><li>🧠 The pain of duplicating template scripts in each project of Gradle</li><li>🧪 What acagent does and why it exists</li><li>🔌 How the plugin simplified everything</li><li>📦 How I published both to Gradle’s ecosystem like a boss</li></ul><p>Let’s start with the mess.</p><h3>🧨 Gradle Scripting: The Default State of Suffering</h3><p>You’ve probably seen (or written) code like this:</p><pre>tasks.test {<br>    jvmArgumentProviders.add(CommandLineArgumentProvider {<br> var dd = configurations.testRuntimeClasspath.get().files.find {<br>     it.name.contains(&quot;asserts-agent&quot;)<br> }<br> listOf(&quot;-javaagent:${dd}&quot;)<br>    })<br>}kot</pre><p>Sounds simple, right?</p><p>But wait… where is agentJar coming from? What if you&#39;re using multiple test tasks? What if you need the agent version to match across modules?</p><p>Soon you’re playing with:</p><ul><li>configurations.create(...)</li><li>dependencies.add(...)</li><li>.getSingleFile()</li><li>and possibly passing properties from the command line or gradle.properties</li></ul><p>And <em>that’s</em> when you’re just trying to <strong>run your unit tests</strong>.</p><p>This was the problem I faced with acagent.<br>🧰 Building the Plugin: Simplicity Is the Real Power</p><p>Plugins are reusable Gradle behaviors. So I turned my JVM agent injection logic into this tiny bit of code:</p><pre>plugins {<br>    id(&quot;com.shivohamai.testing.tools.junit5.assertscounter.plugin&quot;) version &quot;1.0.0&quot;<br>}</pre><p>Boom 💥 — no more custom scripts, no need to think about agent paths, nodoFirst, nojvmArgs, no brittle hacks.</p><h3>🚚 Publishing It All: Agent + Plugin, the Clean Way</h3><p>Publishing Gradle artifacts — especially plugins — can feel like deciphering ancient runes. But with the right tools, it’s elegant.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lUklu03IMFFx-44Q" /></figure><h3>The Strategy</h3><p>I published both:</p><ul><li>asserts-counter-agent – to Maven Central / GitHub Packages</li><li>asserts-counter-plugin – to Gradle Plugin Portal + GitHub Packages</li></ul><blockquote><em>✅ Everything uses the </em><strong><em>same version number</em></strong><em>, to keep compatibility simple.</em></blockquote><h3>🚚 Tooling Used &amp; Publishing Snippet</h3><h4>a) 🔧 Agent: ACAgent</h4><p>The original implementation is <em>not</em> built as a Gradle plugin — it’s a standalone Java agent project packaged with plain Gradle (and optional Maven). It uses bytecode transformation and emits output via a shutdown hook, logging the total assertion count.</p><p>There’s <em>no Gradle plugin marker artifact or Gradle Plugin Portal publication</em>. Instead, you:</p><ol><li>Build the agent JAR (`asserts-counter-agent-xx-SNAPSHOT`)</li><li>Place or publish it in your GitHub Packages artifact repository</li></ol><p>No Gradle plugin infrastructure is defined — only building and testing logic for the agent itself.</p><h4>b) 🔧 Plugin: ACPlugin</h4><h4>🎼 The Conductor: How ACPlugin.kt Orchestrates Everything</h4><p>Welcome to the <strong>heart of our Gradle plugin</strong>: the ACPlugin.kt file.</p><p>If our plugin were a <strong>stage play</strong>, this file would be the <strong>director</strong>. It doesn’t have many lines of its own, but its job is to make sure every other piece of logic <strong>performs its role at the exact right time</strong>. It’s the main entry point that Gradle calls when a user applies our plugin in their build.gradle.kts file.</p><p>Let’s look at the apply method — the &quot;main script&quot; that our director follows. It’s a simple, three-step recipe.</p><h3>🧠 The Code: ACPlugin.kt</h3><pre>open class ACPlugin : Plugin&lt;Project&gt; {<br>    override fun apply(aProject: Project) {<br>        // Step 1: Apply bonus plugins for the user<br>        AddExtraPlugins().apply(aProject)</pre><pre>        // Step 2: Prepare a special place for our Java Agent<br>        val lAgentJARConf = prepareAgentJARConf(aProject)</pre><pre>        // Step 3: Find all test tasks and tell them to use the agent<br>        prepareTestTask(aProject, lAgentJARConf)<br>    }<br>}</pre><h3>🛠️ Step 1: AddExtraPlugins() – The Bonus Toolbelt</h3><p>The first thing our plugin does is give the developer <strong>a little something extra</strong>.</p><p>The AddExtraPlugins class (from AddExtraPlugins.kt) <strong>automatically applies</strong> two popular community plugins:</p><ul><li>com.github.ben-manes.versions<br> ➤ Run ./gradlew dependencyUpdates to see which of your dependencies have new versions.</li><li><a href="https://github.com/patrikerdes/gradle-use-latest-versions-plugin">se.patrikerdes.use-latest-version</a>s<br> ➤ Run ./gradlew useLatestVersions to automatically update your dependencies.</li></ul><p>By including these, our plugin becomes a <strong>“convention plugin”</strong>, bundling helpful tools and saving developers from repetitive update of jars.</p><h3>🧰 Step 2: prepareAgentJARConf() – A Private Toolbox for the Agent</h3><p>Next, we prep our <strong>Java Agent</strong>, a special JAR that can <strong>instrument code at runtime</strong>.</p><p>But we can’t just throw this agent into the project’s regular dependencies. That would:</p><ul><li>Risk <strong>version conflicts</strong></li><li><strong>Pollute</strong> the user’s classpath</li></ul><p>So prepareAgentJARConf (from PrepareAgentJARConf.kt) solves this elegantly.</p><p>It creates a <strong>private, isolated Gradle Configuration</strong> — think of it as a <strong>secret toolbox</strong> that only our plugin knows about. We place the agent JAR in this toolbox and return a handle to it, safely <strong>isolated from the user’s codebase</strong>.</p><h3>🎯 Step 3: prepareTestTask() – The Main Event</h3><p>With the agent tucked away safely in its private toolbox, it’s time to get to work.</p><p>The prepareTestTask function (from PrepareTestTask.kt) performs two crucial jobs:</p><ol><li><strong>Finds all test tasks</strong><br> ➤ It scans the project for every task of type Test.</li><li><strong>Instruments them</strong><br> ➤ For each test task, it appends the -javaagent:&lt;agent-path&gt; argument to the JVM command line — pointing to our previously stored agent.</li></ol><p>This is what <strong>activates</strong> the assertion-counting magic. When the user runs:</p><pre>gradlew test</pre><p>… the JVM spins up <strong>with our agent attached</strong>, ready to count every assertion made during the test run.</p><h3>🎬 In Summary: A Clean 3-Step Playbook</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/354/1*GKAyb87tGw2u0IpDznBNPA.png" /></figure><p>The ACPlugin class doesn’t do heavy lifting itself. Instead, it acts as a <strong>conductor</strong>, delegating each responsibility to specialist helpers. Its execution pattern is both clear and extensible:</p><ol><li><strong>Enhance</strong> → Add helpful tools for developers</li><li><strong>Isolate</strong> → Safely prepare the agent</li><li><strong>Activate</strong> → Wire the agent into the test tasks</li></ol><p>This <strong>clean separation of concerns</strong> makes the plugin <strong>easier to understand, maintain, and evolve</strong> — whether you’re debugging, adding features, or onboarding new contributors.</p><h3>🎁 Bonus: Want to Add Your Own “Instruments”?</h3><p>Because of how modular this setup is, adding new functionality — like reporting, dashboards, or alternate agents — is as simple as dropping a new “player” into the orchestra and letting ACPlugin cue them in.</p><p>Stay tuned for more behind-the-scenes of how this orchestra plays in perfect harmony. 🎻</p><h3>🪜 From Script to Plugin: Why It Matters</h3><p>You might wonder — is it worth making a plugin for something so “small”?</p><p><strong>Absolutely. Here’s why:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/923/1*_G8S6dvFi4aoUwsZOeCqeg.png" /></figure><p>Plugins are like npm packages for your Gradle world — little reusable spells that prevent madness.</p><p>To understand its usage in real projects, refer to how it’s implemented in my other project: sout2log.<br> You can find more details in this article: <a href="https://medium.com/@nagendra.raja/hacking-the-console-3-sneaky-junit-5-tricks-to-tame-system-out-no-legacy-code-harmed-f463bb8d19fc">Hacking the Console – 3 Sneaky JUnit 5 Tricks to Tame System.out (No Legacy Code Harmed)</a>.</p><h3>🧙‍♂️ Final Words: Plugins = Developer Sorcery</h3><p>When Gradle Scripts Get Ugly, Write a Plugin: the Clean Way</p><p>What started as a neat agent became a shareable plugin, and the journey from scripting to plugin taught me:</p><ul><li>Gradle plugins <strong>aren’t just for big infra</strong></li><li>Tiny plugins can vastly improve DX</li><li>If you’re copying 5+ lines across projects, <strong>stop and pluginize</strong></li></ul><h3>📧 Reach Me</h3><p>Want to improve legacy code maintainability, CI pipelines, or Gradle automation?</p><p>Think of me as a Code Doctor. For 20+ years, I’ve helped CxOs and engineers bring old codebases back to life — reviewing over 8 billion lines of code and applying precision tooling to modernize them for the future.</p><p>Let’s talk: <a href="http://www.shivohamai.com/services">shivohamai.com</a> | <a href="http://www.linkedin.com/in/nagkumar">LinkedIn</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=64ac682e92b0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Your JUnit Tests Are Lying to You!]]></title>
            <link>https://medium.com/@nagendra.raja/your-junit-tests-are-lying-to-you-ccda6970b5bc?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/ccda6970b5bc</guid>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Tue, 22 Jul 2025 08:17:32 GMT</pubDate>
            <atom:updated>2025-07-26T19:25:50.545Z</atom:updated>
            <content:encoded><![CDATA[<h4>- and why counting Assertions Is the Plot Twist You (not even <a href="https://junit.org/">JUnit</a>) didn’t see coming</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DS9YmRxPhf7bgUMi" /></figure><p>Ah, the sweet sound of success.</p><p>You hit <strong>Run Tests</strong>, and in mere seconds…</p><p>✅ <strong>Green Bar. All tests passed. High-fives all around. Ship it!</strong></p><p>But wait…</p><p>What if I told you that the green bar is lying?</p><p>Yes, dear developer. You may be the victim of one of the oldest scams in unit testing history.</p><blockquote><strong><em>The Green Bar Fallacy.</em></strong></blockquote><h3>🚨 The Illusion of “Passing” Tests</h3><p>JUnit is amazing.<br> It’s the rock-solid foundation beneath countless Java projects. But it has one <em>tiny little flaw</em>:</p><blockquote><em>It doesn’t care whether your test actually </em><strong><em>tests</em></strong><em> anything.</em></blockquote><p>Let’s peek at this totally “passing” test:</p><pre>@Test<br>void shouldProcessDataWithoutErrors() {<br>    MyService service = new MyService();<br>    InputData data = new InputData(&quot;test&quot;);<br>    service.process(data);<br>    // That&#39;s it? No asserts? Nothing?<br>}</pre><p>When you run this, JUnit nods approvingly.<br> “One test passed! ✅ as JUnit counts every method annotated as @Test”</p><p>But what did it really <em>prove</em>?</p><ul><li>Did we check any results?</li><li>Did we verify the state changed?</li><li>Did we make sure it didn’t do something evil, like delete the database?</li></ul><p>Nope.</p><p>We just said, “Run this code… and hope for the best.”</p><blockquote><em>It’s like going to a doctor, and the doctor just says:<br> “You walked in without falling over? Looks like you’re healthy!”</em></blockquote><h3>🧟‍♂️ Beware the Zombie Tests</h3><p>These “assertion-less” tests are everywhere.<br> Especially in old, sprawling codebases full of patchwork patches and quick fixes.</p><p>They haunt your test suite. They pass. Always.</p><p>They give a false sense of safety — and worse, your <strong>code coverage tools</strong> might even say you’re at <strong>100%!</strong></p><p>But without <strong>assertions</strong>, those tests are just… wasting time.</p><h3>✅ A Better Way: Count Assertions</h3><p>Forget test count. Forget line coverage.</p><p>Want a real measure of test quality?</p><blockquote><strong><em>Count how many assertions actually ran.</em></strong></blockquote><p>That’s it. That’s the whole magic.</p><p>If you’ve got 1,000 tests and only 120 assertions, you don’t need more tests — you need better ones.</p><h3>🤔 “Okay, but… how do I count assertions?”</h3><p>You <em>could</em> manually sprinkle counters into every assertEquals, assertTrue, and assertThat...</p><p>😒 But come on, that’s tedious.</p><p>Let’s do it the fun, powerful, JVM-hacking way.<br><strong>Java Agent to the easy rescue</strong></p><h3>JVM Magic: Using a Java Agent</h3><p>A Java Agent is like a secret spellbook.<br> It lets you <strong>change bytecode</strong> as classes load — without touching source code!</p><p>So here’s the plan:</p><ol><li>Wait for JUnit’s Assertions class to load.</li><li>Inject a tiny line of code into every assert method.</li><li>Increment a counter every time an assertion runs.</li></ol><p>Boom! You now have a <strong>stealthy assertion counter</strong> running across your entire test suite.</p><h3>Let’s Build the Agent!</h3><h3>1. The Entry Point: AssertsAgent.java</h3><pre>public class AssertsAgent {<br>    public static void premain(String agentArgs, Instrumentation inst) {<br>        System.out.println(&quot;✅ Starting AssertsAgent to count JUnit 5 assertions...&quot;);<br>        inst.addTransformer(new AssertsTransformer());<br>Runtime.getRuntime().addShutdownHook(new Thread(() -&gt; {<br>            System.out.println(&quot;-------------------------------------------------&quot;);<br>            System.out.printf(&quot;📊 Total JUnit assertions executed: %d%n&quot;, AssertsCounter.getCount());<br>            System.out.println(&quot;-------------------------------------------------&quot;);<br>        }));<br>    }<br>}</pre><p>This is the heart of your agent.<br>It hooks into the JVM <em>before</em> anything runs.</p><h3>2. The Counter: AssertsCounter.java</h3><pre>import java.util.concurrent.atomic.AtomicInteger;<br>public class AssertsCounter {<br>    private static final AtomicInteger counter = new AtomicInteger(0);<br>    public static void count() {<br>        counter.incrementAndGet();<br>    }<br>    public static int getCount() {<br>        return counter.get();<br>    }<br>}</pre><p>Simple. Thread-safe. Just does the job.</p><h3>3. The Transformer: AssertsTransformer.java</h3><p>This is where the bytecode trickery happens.<br> We use Javassist to modify the Assertions class.</p><pre>public class AssertsTransformer implements ClassFileTransformer {<br>    private static final String JUNIT5_ASSERTIONS_CLASS = &quot;org.junit.jupiter.api.Assertions&quot;;<br>@Override<br>    public byte[] transform(ClassLoader loader, String className, Class&lt;?&gt; classBeingRedefined,<br>                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {<br>        if (!className.replace(&#39;/&#39;, &#39;.&#39;).equals(JUNIT5_ASSERTIONS_CLASS)) {<br>            return null;<br>        }<br>        try {<br>            ClassPool cp = ClassPool.getDefault();<br>            CtClass cc = cp.get(JUNIT5_ASSERTIONS_CLASS);<br>            for (CtMethod method : cc.getDeclaredMethods()) {<br>                method.insertBefore(&quot;com.yourcompany.asserts.AssertsCounter.count();&quot;);<br>            }<br>            return cc.toBytecode();<br>        } catch (Exception e) {<br>            System.err.println(&quot;❌ Failed to transform class &quot; + className);<br>            e.printStackTrace();<br>            return null;<br>        }<br>    }<br>}</pre><p>Don’t forget to update the package com.yourcompany.asserts to your actual one.</p><h3>🛠️ How to Run It</h3><ol><li>Package everything into a JAR (e.g., asserts-agent.jar)</li><li>Run your tests with jvm org option:</li></ol><pre>-javaagent:/path/to/asserts-agent.jar</pre><h3>Example Output:</h3><pre>✅ Starting AssertsAgent to count JUnit 5 assertions...<br>[INFO] Tests run: 6, Failures: 0<br>-------------------------------------------------<br>📊 Total JUnit assertions executed: 17<br>-------------------------------------------------<br><br>testing\junit5\assertcounter\AssertsAgent&gt;gradle --rerun-tasks clean jar test<br>=== Assert Method Usage Report ===<br>com.shivoham.tools.junit5.assertcounter.tests.TestAssertJAsserts#assertJAssertionsLoop : 1<br>org.assertj.core.api.Assertions#assertThat                   : 150<br>org.assertj.core.api.AssertionsForClassTypes#assertThat      : 150<br>org.hamcrest.MatcherAssert#assertThat                        : 30<br>org.junit.jupiter.api.AssertEquals#assertEquals              : 23<br>org.junit.jupiter.api.AssertFalse#assertFalse                : 23<br>org.junit.jupiter.api.AssertNotEquals#assertNotEquals        : 23<br>org.junit.jupiter.api.AssertNotNull#assertNotNull            : 23<br>org.junit.jupiter.api.AssertTrue#assertTrue                  : 23<br>org.junit.jupiter.api.Assertions#assertEquals                : 23<br>org.junit.jupiter.api.Assertions#assertFalse                 : 23<br>org.junit.jupiter.api.Assertions#assertNotEquals             : 23<br>org.junit.jupiter.api.Assertions#assertNotNull               : 23<br>org.junit.jupiter.api.Assertions#assertTrue                  : 23<br>-------------------------------------------------------<br>Total assert* method calls: 561<br><br>&gt; Task :test<br>🔍 Test Summary:<br> - 3 tests executed<br> - 3 succeeded<br> - 0 failed<br> - 0 skipped</pre><p>Just like that — you’re seeing your <strong>real</strong> test signal.</p><h3>Pro Tip: Fail If Total Assertions in the build are == 0</h3><p>Want to <strong>catch empty test files</strong> automatically?</p><p>Add a post-test check. If assertion count == 0, fail the build.</p><p>Because a test suite with 500 empty tests isn’t quality — it’s cargo cult testing.</p><h3>🎯 Why This Matters</h3><p>Tests aren’t about <em>running</em> code.<br> They’re about <strong>verifying</strong> it behaves correctly.</p><blockquote><em>A test without an assertion is just a demo.</em></blockquote><p>By counting assertions, you’re measuring <strong>actual verification</strong>, not just motion.</p><h3>But Wait… Assertion Count Can Lie Too?</h3><p>So, we’ve added an agent that counts assertions. We feel good. The green bar is no longer tricking us.</p><p>But then… someone does this:</p><pre>@Test<br>void shouldAlwaysPass() {<br>    assertTrue(true);  // 🙄 Come on, man.<br>}</pre><p>Or worse:</p><pre>assertEquals(&quot;&quot;, &quot;&quot;, &quot;empty&quot;);  // What are we even verifying here?</pre><p>👉 These assertions are syntactically fine, but <strong>semantically useless</strong>.</p><blockquote><em>This is like installing a smoke detector — and gluing the “no fire” light on.</em></blockquote><p>So yes, even <strong>assert count can lie</strong> if the assertions are meaningless.</p><p>We’ve traded one false sense of security for another.</p><h3>🧠 Next Level: Expression-Aware Assertion Analysis (Agent++)</h3><p>Here’s where things get <em>really</em> cool (and a little nerdy):</p><p>We can enhance our Java Agent to not just <strong>count</strong> assertions but also <strong>log what was being asserted</strong>.</p><p>Think of it like:</p><ul><li>✅ assertEquals(expected, actual) — Okay, real test!</li><li>❌ assertTrue(true) — Throw this into the trash.</li><li>❓ assertNotNull(&quot;string&quot;) — Hmm, maybe, but suspicious.</li></ul><p>To do this, we can:</p><ul><li>Use <strong>Javassist</strong> or <strong>ASM</strong> to capture method arguments.</li><li>Log the <strong>types</strong>, <strong>constants</strong>, and maybe even <strong>stringified expressions</strong> being asserted.</li><li>Flag suspicious patterns like:</li><li>assertEquals(&quot;some literal&quot;, &quot;some literal&quot;)</li><li>assertTrue(true)</li><li>assertNotNull(&quot;abc&quot;)</li></ul><p>Yes, this isn’t trivial — we’re not working with source code but <strong>bytecode</strong> — but with some clever stack tracing and class metadata, we can get <strong>a lot</strong> of insight.</p><blockquote><em>Imagine a test report that not only says “You ran 37 assertions,” but also warns:<br> “⚠️ 12 of them are useless.”</em></blockquote><p>Boom. <strong>Assertion quality meets observability.</strong></p><h3>🔥 Coming Soon from #CodeDoctors…</h3><p>We’re cooking up an extended version of this agent — let’s call it <strong>AssertsAgent++</strong> — that doesn’t just count assertions, but audits them:</p><ul><li>Flags suspicious patterns</li><li>Shows assertion intent</li><li>Offers insights into testing quality beyond the numbers</li></ul><p>If that sounds like magic, you need it in your CI/CD pipeline…<br> Well, you know where to find us. 💬</p><h3>📎 Full Working Code</h3><p>You can get the full source code right here:<br> 👉 <a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/AssertsCounter/ACAgent">https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/AssertsCounter/ACAgent</a></p><h3>🩺 PS: Need Help with Legacy Code?</h3><p>Old code is tricky. Assertion-less tests are just the beginning.<br> That’s where <strong>#CodeDoctors</strong> come in.</p><p>We specialize in:</p><ul><li>Diagnosing untestable code</li><li>Curing flaky test suites</li><li>Making legacy code maintainable again</li></ul><p>Want your code to be robust, testable, and future-ready?</p><p><strong>Reach out to us! </strong><a href="http://www.shivohamai.com/services">http://www.shivohamai.com/services</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ccda6970b5bc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hacking the Console: 3 Sneaky JUnit 5 Tricks to Tame System.out & System.err]]></title>
            <link>https://medium.com/@nagendra.raja/hacking-the-console-3-sneaky-junit-5-tricks-to-tame-system-out-no-legacy-code-harmed-f463bb8d19fc?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/f463bb8d19fc</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[junit-5]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Tue, 22 Jul 2025 03:12:33 GMT</pubDate>
            <atom:updated>2025-07-22T03:28:10.029Z</atom:updated>
            <content:encoded><![CDATA[<h3>No Legacy Code Harmed!</h3><p>You maintain a critical piece of legacy code. It’s a mysterious, powerful beast that runs in production, but it has one terrifying feature:<br>➡️ It communicates <strong>exclusively through </strong><strong>System.out.println and </strong><strong>System.err.println.</strong></p><p>There are no log files, no structured events — just a firehose of strings sprayed directly to the console.</p><p>Your mission, should you choose to accept it, is to <strong>write meaningful unit tests</strong> for this code. The catch?</p><blockquote><em>You are </em><strong><em>not allowed to change a single line</em></strong><em> of the original source.</em></blockquote><p>How can you possibly test something that you can’t see, hear, or touch?<br>How do you assert that the correct &quot;User saved successfully&quot; message was printed, or that a specific error was sent to System.err?</p><p><strong>Fear not!</strong><br>With JUnit 5, you have several powerful techniques at your disposal. Today, we’ll unlock <strong>three of them</strong>, from the classic manual approach to the elegant, modern solution.</p><h3>🔍 The Core Problem: Why Is System.out So Hard to Test?</h3><p>System.out and System.err are <strong>global, static </strong><strong>PrintStream objects</strong>.<br>They are a shared resource across the entire JVM.</p><p>In a test environment, this means any output from your code: — goes into the <strong>same black hole</strong> as the output from your build tool, — other tests, — and the JVM itself.</p><p>👉 You can’t easily isolate and inspect the specific strings your method printed.</p><p>Our goal?</p><blockquote><em>Intercept this output </em><strong><em>before it reaches the console.</em></strong></blockquote><p>Let’s dive in 👇</p><h3>✅ Method 1: The Classic Redirect (Pure JUnit 5)</h3><p>This is the foundational, <strong>do-it-yourself</strong> approach — no dependencies involved.</p><h3>🧠 The Concept</h3><ol><li><strong>Before the test</strong>: Save the original System.out and System.err.</li><li>Replace them with a ByteArrayOutputStream, which acts like a memory buffer.</li><li><strong>Run the test</strong>: The legacy code prints to your in-memory buffer.</li><li><strong>After the test</strong>: Assert the buffer contents and <strong>restore the original streams</strong>.</li></ol><h3>🧾 Legacy Code</h3><pre>// src/main/java/com/example/legacy/LegacyApp.java<br>public class LegacyApp {<br>    public void run(boolean succeed) {<br>        if (succeed) {<br>            System.out.println(&quot;SUCCESS: Operation completed.&quot;);<br>        } else {<br>            System.err.println(&quot;ERROR: Operation failed.&quot;);<br>        }<br>    }<br>}</pre><h3>✅ Test Code</h3><pre>// src/test/java/com/example/legacy/sys/LegacyAppTest.java<br>import org.junit.jupiter.api.*;<br>import java.io.ByteArrayOutputStream;<br>import java.io.PrintStream;<br>import static org.junit.jupiter.api.Assertions.assertEquals;</pre><pre>class LegacyAppTest {<br>    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();<br>    private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();<br>    private final PrintStream originalOut = System.out;<br>    private final PrintStream originalErr = System.err;</pre><pre>    @BeforeEach<br>    void setUpStreams() {<br>        System.setOut(new PrintStream(outContent));<br>        System.setErr(new PrintStream(errContent));<br>    }</pre><pre>    @AfterEach<br>    void restoreStreams() {<br>        System.setOut(originalOut);<br>        System.setErr(originalErr);<br>    }</pre><pre>    @Test<br>    void testSuccessOutput() {<br>        new LegacyApp().run(true);<br>        assertEquals(&quot;SUCCESS: Operation completed.\n&quot;, outContent.toString());<br>        assertEquals(&quot;&quot;, errContent.toString());<br>    }</pre><pre>    @Test<br>    void testErrorOutput() {<br>        new LegacyApp().run(false);<br>        assertEquals(&quot;ERROR: Operation failed.\n&quot;, errContent.toString());<br>        assertEquals(&quot;&quot;, outContent.toString());<br>    }<br>}</pre><h3>👍 Pros</h3><ul><li>✅ Zero dependencies</li><li>✅ Easy to understand</li></ul><h3>👎 Cons</h3><ul><li>❌ Boilerplate in every test class</li></ul><h3>🔁 Method 2: The Log4j Bridge</h3><p>What if you could treat System.out.println like a <strong>proper logging event</strong>?</p><p>This method redirects system streams to a <strong>Log4j logger</strong>, allowing you to use logging tools to test legacy code as if it had real log messages.</p><h3>🧠 The Concept</h3><ol><li>Create a custom PrintStream that calls logger.info() or logger.error() instead of printing.</li><li>Redirect System.out and System.err to it.</li><li>Use a <strong>Log4j test appender</strong> to capture log events and assert them.</li></ol><h3>🛠 Bridge Class</h3><pre>// src/main/java/com/example/legacy/log/LoggingOutputStream.java<br>import org.apache.logging.log4j.Logger;<br>import java.io.OutputStream;<br>import java.io.PrintStream;</pre><pre>public class LoggingOutputStream extends PrintStream {<br>    private final Logger logger;</pre><pre>    public LoggingOutputStream(Logger logger, OutputStream out) {<br>        super(out);<br>        this.logger = logger;<br>    }</pre><pre>    @Override<br>    public void println(String line) {<br>        logger.info(line); // or logger.error based on stream<br>    }<br>}</pre><h3>✅ Test Code (Using Log4j’s ListAppender)</h3><pre>// src/test/java/com/example/legacy/log/LegacyAppLog4jTest.java<br>import org.apache.logging.log4j.*;<br>import org.apache.logging.log4j.core.Appender;<br>import org.apache.logging.log4j.core.layout.PatternLayout;<br>import org.apache.logging.log4j.test.appender.ListAppender;<br>import org.junit.jupiter.api.*;</pre><pre>import java.io.PrintStream;</pre><pre>import static org.assertj.core.api.Assertions.assertThat;</pre><pre>class LegacyAppLog4jTest {<br>    private static final Logger testLogger = LogManager.getLogger(LegacyAppLog4jTest.class);<br>    private static ListAppender listAppender;<br>    private static final PrintStream originalOut = System.out;</pre><pre>    @BeforeAll<br>    static void setup() {<br>        listAppender = new ListAppender(&quot;List&quot;, null,<br>                PatternLayout.createDefaultLayout(), false, false);<br>        listAppender.start();</pre><pre>        ((org.apache.logging.log4j.core.Logger) testLogger).addAppender(listAppender);</pre><pre>        System.setOut(new LoggingOutputStream(testLogger, originalOut));<br>    }</pre><pre>    @AfterAll<br>    static void tearDown() {<br>        System.setOut(originalOut);<br>    }</pre><pre>    @BeforeEach<br>    void clearAppender() {<br>        listAppender.clear();<br>    }</pre><pre>    @Test<br>    void testSuccessIsLoggedAsInfo() {<br>        new LegacyApp().run(true);<br>        assertThat(listAppender.getMessages())<br>            .hasSize(1)<br>            .contains(&quot;SUCCESS: Operation completed.&quot;);<br>    }<br>}</pre><h3>👍 Pros</h3><ul><li>✅ Treats System.out as real log events</li><li>✅ Treats System.erras real log events, by using System.<em>setErr</em>()</li><li>✅ Plays well with modern log pipelines</li></ul><h3>👎 Cons</h3><ul><li>❌ Higher setup complexity</li><li>❌ Requires Log4j internals knowledge</li></ul><h3>⚡ Method 3: The Modern SLF4J Way (With System-Lambda)</h3><p>This is the <strong>cleanest</strong> and most <strong>maintainable</strong> solution.</p><p>It uses the <a href="https://github.com/stefanbirkner/system-lambda">System Lambda</a> library to intercept and return output from System.out/System.err via lambdas.</p><h3>🧠 The Concept</h3><p>Wrap your test code in a lambda.<br>The library: — redirects the stream, — runs your code, — captures the output, — restores the stream.</p><p>All in one call.</p><h3>🛠 Gradle Dependency</h3><pre>testImplementation(&quot;com.github.stefanbirkner:system-lambda:1.2.1&quot;)</pre><h3>✅ Test Code</h3><pre>// src/test/java/com/example/legacy/lambda/LegacyAppLambdaTest.java<br>import com.github.stefanbirkner.systemlambda.SystemLambda;<br>import org.junit.jupiter.api.Test;<br>import static org.junit.jupiter.api.Assertions.assertEquals;</pre><pre>class LegacyAppLambdaTest {</pre><pre>    @Test<br>    void testSuccessOutputWithLambda() throws Exception {<br>        String output = SystemLambda.tapSystemOut(() -&gt; {<br>            new LegacyApp().run(true);<br>        });<br>        assertEquals(&quot;SUCCESS: Operation completed.\n&quot;, output);<br>    }</pre><pre>    @Test<br>    void testErrorOutputWithLambda() throws Exception {<br>        String error = SystemLambda.tapSystemErr(() -&gt; {<br>            new LegacyApp().run(false);<br>        });<br>        assertEquals(&quot;ERROR: Operation failed.\n&quot;, error);<br>    }<br>}</pre><h3>👍 Pros</h3><ul><li>✅ Beautifully concise</li><li>✅ Zero boilerplate</li><li>✅ Foolproof redirection and restoration</li></ul><h3>👎 Cons</h3><ul><li>❌ Requires adding a lightweight library</li></ul><h3>🧠 Comparison Summary</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3uAE5uwufgnBxvDg4ekHUw.png" /></figure><h3>💬 Final Thoughts</h3><p>Testing legacy code doesn’t have to be a nightmare.</p><p>By <strong>intercepting the console output</strong>, you can bring even the most ancient, logging-challenged code under the safety of a modern test harness.</p><blockquote><em>Don’t rewrite legacy — </em><strong><em>wrap and test it cleverly till you take control of legacy with these kind of test cases safety net.</em></strong></blockquote><h3>🧪 Need Help With Legacy Code?</h3><p>If your codebase talks in printlns, hides behind static methods, or screams “don’t touch me!” in every commit — you’re not alone.</p><p>Come talk to the <strong>#CodeDoctors</strong> 🧑‍⚕️💻 — specialists in testability, observability, maintainability, and any NFRs related to legacy systems.</p><p>Whether it’s: — modernizing untestable logic, — introducing clean logging patterns, — or designing safe refactors…</p><p>We help you <strong>stabilize, simplify, and scale</strong> without a risky rewrite.</p><p>👉 <strong>DM us at </strong><a href="http://www.shivohamai.com/services"><strong>ShivohamAI</strong> </a>— or leave a comment. Let’s tame your legacy beast together.</p><p><strong>GitHub Repo CODE</strong>:<br> <a href="https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/logs/sys2log/sys2log">https://github.com/nagkumar/java/tree/main/tutorials/testing/junit5/logs/sys2log/sys2log</a></p><p><strong>#Java #JUnit5 #Testing #SystemOut #LegacyCode #CleanCode #CodeDoctors #ShivohamAI #SoftwareCraftsmanship</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f463bb8d19fc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testability of ‘System.exit(42)’ : The Clean Way]]></title>
            <link>https://medium.com/@nagendra.raja/testability-of-system-exit-42-the-clean-way-a89bf7241e39?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/a89bf7241e39</guid>
            <category><![CDATA[junit-5]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Sat, 19 Jul 2025 15:38:34 GMT</pubDate>
            <atom:updated>2025-07-19T15:55:27.991Z</atom:updated>
            <content:encoded><![CDATA[<h3>Testability of ‘System.exit(42)’ : The Clean Way</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wLLrQ7CGEdcZPdfK" /><figcaption>Don’t touch your legacy</figcaption></figure><h3>Introduction</h3><p>Imagine you’re testing a legacy Java application, and BAM! — the application calls System.exit(1) during a test in a test suite that have 100s of testcases.</p><p>Just like that, your entire test suite halts. No logs. No cleanup. No mercy. In Gradle, one may see build failure with misleading exception info</p><pre>[Incubating] Problems report is available at: file:///junit5exit_hw/build/reports/problems/problems-report.html<br>FAILURE: Build failed with an exception.<br>* What went wrong:<br>Could not determine the dependencies of task &#39;:test&#39;.<br>&gt; Could not resolve all dependencies for configuration &#39;:testRuntimeClasspath&#39;.<br>   &gt; Failed to calculate the value of task &#39;:compileTestJava&#39; property &#39;javaCompiler&#39;.<br>      &gt; Cannot find a Java installation on your machine (Windows 11 10.0 amd64) matching: {languageVersion=24, vendor=any vendor, implementation=vendor-specific, nativeImageCapable=false}. Toolchain download repositories have not been configured.<br>* Try:<br>&gt; Learn more about toolchain auto-detection and auto-provisioning at <a href="https://docs.gradle.org/8.14.1/userguide/toolchains.html#sec:auto_detection.">https://docs.gradle.org/8.14.1/userguide/toolchains.html#sec:auto_detection.</a><br>&gt; Learn more about toolchain repositories at <a href="https://docs.gradle.org/8.14.1/userguide/toolchains.html#sub:download_repositories.">https://docs.gradle.org/8.14.1/userguide/toolchains.html#sub:download_repositories.</a><br>&gt; Run with --stacktrace option to get the stack trace.<br>&gt; Run with --info or --debug option to get more log output.<br>&gt; Run with --scan to get full insights.<br>&gt; Get more help at <a href="https://help.gradle.org.">https://help.gradle.org.</a></pre><pre>Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.</pre><pre>You can use &#39;--warning-mode all&#39; to show the individual deprecation warnings and determine if they come from your own scripts or plugins.</pre><pre>For more on this, please refer to <a href="https://docs.gradle.org/8.14.1/userguide/command_line_interface.html#sec:command_line_warnings">https://docs.gradle.org/8.14.1/userguide/command_line_interface.html#sec:command_line_warnings</a> in the Gradle documentation.<br>BUILD FAILED in 1s</pre><p>The Gradle build logs above are unrelated to the core problem. When the main code executes System.exit(), it terminates the entire Java Virtual Machine process, which includes both the test execution environment and the Gradle build process.</p><p>Welcome to the wild world of testing legacy code that was never meant to be tested.</p><p>In this article, we’ll unravel the mystery of how System.exit(...) in main app code does not impact unit tests using <a href="https://github.com/tginsberg/junit5-system-exit">tginsberg/junit5-system-exit</a> library, also look at the evolution of solutions around these <strong><em>testability </em></strong>problems of the code under test. I shall explore <strong>how the framework works</strong>, and top it off with why your codebase might be crying out for some professional <strong>Code Doctorship</strong> from <a href="http://www.shivohamai.com/services">ShivohamAI</a>.</p><p>And yes, we’ll have some AI fun along the way. 🧠🤖</p><h3>The Problem: Why System.exit() is a Test Killer</h3><p>When System.exit() is invoked, the JVM process terminates. This might make sense in production (e.g., to signal an unrecoverable error), but it’s <strong>disastrous during tests</strong>.</p><pre>public class App {<br>    public static void main(String[] args) {<br>        if (args.length == 0) {<br>            System.exit(1); // 👋 Bye bye, JVM<br>        }<br>    }<br>}</pre><p>Try testing this with JUnit 5, and you’ll watch your test runner vanish like Thanos just snapped. 🫰💀</p><blockquote>Legacy Solutions: History of System.exit() Handling</blockquote><h3>1. Don’t Test It (aka the Ostrich Strategy 🐦🕳️)</h3><ul><li>Pretend it doesn’t need testing.</li><li>Hope no bugs exist in those exit paths.</li></ul><blockquote><strong><em>Pro</em></strong><em>: Easy to be unprofessional.</em></blockquote><blockquote><strong><em>Con</em></strong><em>: You have no idea what your app does when it exits. Dangerous in production!</em></blockquote><h3>2. Refactor Everything</h3><ul><li>Inject a wrapper for System.exit() and test using mocks.</li></ul><pre>interface ExitHandler { void exit(int status); }</pre><blockquote><strong><em>Pro</em></strong><em>: Clean, testable.</em></blockquote><blockquote><strong><em>Con</em></strong><em>:</em></blockquote><blockquote><em>Massive refactor. Good luck doing this in legacy code. 😵</em></blockquote><h3>3. SecurityManager Hacks (JUnit 4 Era)</h3><pre>System.setSecurityManager(new NoExitSecurityManager());</pre><blockquote><strong><em>Pro</em></strong><em>: It worked.</em></blockquote><blockquote><strong><em>Con</em></strong><em>: Deprecated and now depricated from JDK 17+, and completely removed from JDK 24</em></blockquote><h3>4. Enter the Hero: junit5-system-exit</h3><p><a href="https://github.com/tginsberg/junit5-system-exit">junit5-system-exit</a> is a library that intercepts calls to System.exit() <strong>without requiring a SecurityManager and </strong>also does not require you to make any changes to legacy code. Instead, it uses bytecode instrumentation to rewrite exit behavior during tests.</p><h3>How It Works (Magic Alert!) ✨</h3><p>Under the hood, it works in a few clever steps:</p><ol><li><strong>Instrumentation Setup</strong>: A JUnit 5 extension is used to bootstrap a Java agent (ExitInterceptingAgent) at runtime.</li><li><strong>Bytecode Rewrite</strong>: The agent finds all System.exit() calls and replaces them with a call to throw a special unchecked exception (ExitException) during the test.</li><li><strong>Assertion Hooks</strong>: You can then use the injected SystemExit object to expect specific exit codes.</li><li><strong>Safety Net</strong>: The JVM doesn’t really exit, but behaves like it did. No threads are killed. Test continues.</li></ol><blockquote><em>⚙️ Think of it like replacing your app’s self-destruct button with a harmless confetti cannon during tests. 🎉</em></blockquote><p>This approach works reliably as long as the instrumentation is allowed access to required classes (e.g., java.lang.Runtime). That&#39;s why --add-opens flags are needed in JDK 17+.</p><p>You can view a HelloWorld working GitHub code reference here: <a href="https://github.com/nagkumar/junit5exit_hw">junit5exit_hw</a>. It demonstrates how to wire everything correctly and validate System.exit(...) behaviors cleanly.</p><h3>Let’s Code: Writing a Test That Handles System.exit</h3><p>Here’s a basic example:</p><pre>import com.ginsberg.junit.exit.ExpectSystemExit;<br>import com.ginsberg.junit.exit.SystemExit;<br>import org.junit.jupiter.api.Test;</pre><pre>public class ExitAppTest {</pre><pre> @Test<br>    @ExpectSystemExitWithStatus(42)<br>    void testSystemExit()<br>    {<br>         new HelloWorld().sayHelloAndExit();<br>    }<br><br>    @Test<br>    @ExpectSystemExitWithStatus(42)<br>    void testSystemExit2() throws InterruptedException<br>    {<br>         new Thread(() -&gt; {<br>             new HelloWorld().sayHelloAndExit();<br>           }).start();<br><br>         Thread.sleep(1000);  // Give it some time to execute<br>    }<br>}</pre><h3>💡 Key Points:</h3><ul><li>ExpectSsytemExitWithStatus argument allows you to <strong>expect</strong> and <strong>assert</strong> the exit status.</li></ul><h3>Diagrams: What Happens Behind the Scenes</h3><h3>Before:</h3><pre>App.main()<br>   |<br>System.exit(1)<br>   |<br> JVM Dies 💀</pre><h3>After using junit5-system-exit:</h3><pre>App.main()<br>   |<br>System.exit(1)<br>   |<br>Exit call intercepted! ➡️ Exception thrown<br>   |<br>Test catches it! ✅</pre><h3>Pros &amp; Cons of junit5-system-exit</h3><h3>✅ Pros</h3><ul><li>✅ No need to refactor legacy code</li><li>✅ No SecurityManager dependency</li><li>✅ Supports exit status assertions too</li><li>✅ Works well with JUnit5 and modern JVMs (with --add-opens)</li></ul><h3>⚠️ Cons</h3><ul><li>⚠️ Relies on bytecode transformation (can be brittle if JVM internals change)</li><li>⚠️ May not work out-of-the-box with all build tools or future JVM versions</li></ul><h3>When Things Break: Common Pitfalls</h3><h3>🛑 Forgot --add-opens</h3><blockquote><em>JVMs with strong encapsulation (JDK 17+) will refuse access unless explicitly allowed.</em></blockquote><h3>🛑 Conflict with Other Bytecode Libraries</h3><blockquote><em>junit5-system-exit uses ASM under the hood. If your codebase or plugins use a different ASM version, override like this:</em></blockquote><pre>dependencies {<br>    testImplementation(&quot;com.ginsberg:junit5-system-exit:2.0.2&quot;)<br>    testImplementation(&quot;org.ow2.asm:asm:9.8&quot;) // Force latest<br>}</pre><h3>Fun With AI: Meet ExitBot 🤖</h3><blockquote><em>“I used to crash JVMs for fun… Now I’m just a misunderstood method.”</em></blockquote><p>ExitBot is our AI mascot who used to end test suites with a bang.</p><p>Now, thanks to junit5-system-exit, ExitBot&#39;s aggression is tamed:</p><pre>@Test<br>@ExpectSystemExit<br>void testWithExitBot() {<br>    System.exit(0);<br>}</pre><h3>Real World Use Case: Legacy Financial App</h3><p>A ShivohamAI client had 2500+ legacy JUnit 4 tests, more than 180+ of which invoked CLI tools with System.exit(1) for bad inputs. Migrating them was a nightmare — until we introduced junit5-system-exit.</p><h3>🔧 Results:</h3><ul><li>96% such tests migrated to JUnit 5 in 1 week and another 1 week for testing</li><li>Zero production changes required</li><li>Full test coverage achieved for critical exit paths</li></ul><h3>Call to Action: Why ShivohamAI?</h3><p>Legacy code is filled with dragons:</p><ul><li><em>System.exit() everywhere</em></li><li><em>Static utilities</em></li><li><em>Magic constants</em></li><li><em>Old JUnit 3/4 + manual asserts</em></li><li><em>Old JDK8 to latest JDK Migrations</em></li><li><em>Too many manual steps to build and release</em></li><li><em>Build taking hours</em></li><li><em>You have too many branches to maintain</em></li></ul><p>At <a href="http://www.shivohamai.com/services">ShivohamAI</a>, we act as <strong>Code Doctors</strong>:</p><p>🩺 Diagnose dev &amp; test pain points<br> 💉 Prescribe modern tools and migration strategies<br> 🚀 Deliver transformed test suites that work without drama</p><blockquote><strong>Need a second opinion on your legacy tests? Call the Code Doctors.</strong></blockquote><h3>Conclusion: Exit without Cry🚪✅</h3><p>Testing System.exit() doesn&#39;t have to be scary. With the right tools, modern JVM flags, and a bytecode touch of magic, you can regain control of your test suite.</p><p>Use junit5-system-exit to:</p><ul><li>Intercept exits safely</li><li>Assert exit codes</li><li>Avoid risky refactorings, especially when there are no tests</li></ul><p>You can also start with the <a href="https://github.com/nagkumar/junit5exit_hw">HelloWorld reference project</a> to see everything wired and working.</p><p>And if your codebase feels like a haunted mansion of tech debt, <a href="http://www.shivohamai.com/services">ShivohamAI</a> is ready with a stethoscope and a scalpel.</p><p>Expert testing practices can save teams from layoffs by addressing the legacy complexity that’s causing customer loss. 💚</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a89bf7241e39" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ Why Your Dependency Scans Are Slow (and Incomplete) Without an NVD API Key]]></title>
            <link>https://medium.com/@nagendra.raja/why-your-dependency-scans-are-slow-and-incomplete-without-an-nvd-api-key-54d29cb04c6b?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/54d29cb04c6b</guid>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Fri, 23 May 2025 14:38:46 GMT</pubDate>
            <atom:updated>2025-05-23T14:47:47.759Z</atom:updated>
            <content:encoded><![CDATA[<p>As developers and DevSecOps engineers, we’re constantly racing to identify and fix security vulnerabilities in our software’s third-party dependencies.</p><p>For the need for speed and cheap, developers rely heavily on open-source libraries and frameworks. But every third-party dependency you include can silently introduce serious vulnerabilities into your project, as each may further depend on other open-source libraries and frameworks.</p><p>That’s where <a href="https://owasp.org/www-project-dependency-check/"><strong>OWASP Dependency-Check</strong></a> comes in.</p><blockquote><em>It doesn’t just list your dependencies — it checks them against the </em><strong><em>National Vulnerability Database (NVD)</em></strong><em> to find </em><strong><em>known security issues</em></strong><em> (CVEs) against the specific version of dependency used by the project/module.</em></blockquote><h3>🔧 What Is Dependency-Check and Why Use It?</h3><p>Dependency-Check is an open-source security tool that:</p><ul><li><strong>Scans your project’s dependencies</strong> (Java JARs — maven, gradle, npm packages, Python modules, etc.)</li><li>Identifies their Common Platform Enumeration (CPE)</li><li>Cross-reference these against the NVD to find known CVEs</li></ul><h3>✅ Why It’s Critical</h3><ul><li><strong>Detects vulnerabilities</strong> like Log4Shell before deployment</li><li><strong>Integrates into CI/CD pipelines</strong> to enforce shift-left security</li><li><strong>Free and open-source</strong>, supporting multiple languages and ecosystems</li></ul><p>But there’s one issue…</p><blockquote><em>Without an </em><strong><em>NVD API key</em></strong><em>, your scans are </em><strong><em>slow</em></strong><em>, </em><strong><em>rate-limited</em></strong><em>, and sometimes incomplete.</em></blockquote><h3>📙 What Is the NVD?</h3><p>The <strong>National Vulnerability Database (NVD)</strong> is the U.S. government’s repository of standardized vulnerability data, maintained by <strong>NIST</strong>.</p><p>When a vulnerability like CVE-2021-44228 (Log4Shell) is discovered:</p><ul><li>It gets cataloged in the NVD</li><li>It is assigned a severity score (CVSS), impact description, and remediation info</li></ul><p>Dependency-Check queries this database to assess whether your dependencies are affected.</p><h3>⏱️ Why It Gets Slow Without an API Key</h3><p>The NVD REST API is rate-limited to prevent abuse:</p><p>Mode Rate Limit (approx.) No API Key ~1.5 requests per second With API Key 5 to 10+ requests per second</p><p><strong>Without an API key</strong>, you hit these endpoints anonymously:</p><pre>https://services.nvd.nist.gov/rest/json/cves/2.0</pre><ul><li>You’re severely throttled</li><li>Scans take 5x to 10x longer</li></ul><h3>⏳ Real-World Example</h3><p>Java project with 120 dependencies:</p><p><strong>Without API Key</strong>:</p><ul><li>~730 CVE lookups</li><li>~24 minutes, includes throttling</li></ul><p><strong>With API Key</strong>:</p><ul><li>Same 730 lookups</li><li>~4–5 minutes</li></ul><p>In real world these looks are much more as high as 300k</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/606/1*CdBRo-Ewuo0hQb8up9NAZA.png" /></figure><h3>🔑 How to Get Your NVD API Key (Free!)</h3><p>Getting a key is easy and free:</p><ol><li>Visit: <a href="https://nvd.nist.gov/developers/request-an-api-key">https://nvd.nist.gov/developers/request-an-api-key</a></li><li>Enter your name and email</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*81uts0-iLwAPfDfIZyfOhg.png" /></figure><p>Receive your API key instantly</p><h3>Use It in Dependency-Check:</h3><pre>export NVD_API_KEY=your_key_here</pre><p>Or in your CI/CD settings:</p><pre>dependency-check --project myapp --scan ./target --nvdApiKey $NVD_API_KEY</pre><h3>⚖️ Is the NVD API Key Free?</h3><p>Yes. 100% free.</p><p>Feature Cost NVD API Key ✅ Free OWASP Dependency-Check ✅ Free, open-source Rate limit bump ✅ Free</p><p>Provided by the U.S. government for public security awareness.</p><h3>🚫 What If Your Key Gets Leaked?</h3><h3>❌ If a hacker gets your key:</h3><ul><li>They can <strong>consume your rate quota</strong></li><li>They <strong>can’t use it to hack systems</strong> (it’s read-only)</li><li>NIST may <strong>throttle or revoke</strong> it if abuse is detected</li></ul><h3>✅ What to Do:</h3><ul><li>Email: <a href="mailto:nvd@nist.gov"><strong>nvd@nist.gov</strong></a></li><li>Request revocation and reissue</li><li>Include your key and reason (e.g., exposed in GitHub)</li></ul><h3>⏲ Does the Key Expire?</h3><ul><li><strong>No fixed expiration</strong> as of now</li><li>Rotate periodically for hygiene</li></ul><h3>💼 Can Organizations Have Multiple Keys?</h3><p>Yes, but with caution:</p><ul><li>Multiple people can request keys (one per team/env)</li><li>Don’t rotate keys to bypass rate limits — NIST monitors abuse</li></ul><h3>Best Practices:</h3><ul><li>One key per environment/team</li><li>Store in CI/CD secrets or env vars</li><li>Monitor usage, rotate if leaked</li></ul><h3>❓ FAQ Recap</h3><p>Question Answer</p><p>Is the NVD API key free? ✅ Yes <br>Can I use multiple keys? ✅ Yes, but don’t abuse <br>Can it be used to hack? ❌ No, it’s read-only public data <br>How do I get a new key? Request via <a href="https://nvd.nist.gov/developers/request-an-api-key">NVD form</a> or email <br>Does the key expire? ❌ Not currently</p><h3>⚙️ How to Configure NVD API Key in Your Gradle Build</h3><p>To make sure Dependency-Check uses your <strong>NVD API key</strong> during scans (and thus speeds up lookups), you need to pass the key to the plugin.</p><h3>Step 1: Add the Dependency-Check Gradle Plugin</h3><p>In your build.gradle file, include:</p><pre>plugins {<br>    id &quot;org.owasp.dependencycheck&quot; version &quot;12.1.1&quot;  // Use latest version<br>}</pre><p>Or if using the older buildscript style:</p><pre>buildscript {<br>    repositories {<br>        mavenCentral()<br>    }<br>    dependencies {<br>        classpath &quot;org.owasp:dependency-check-gradle:12.1.1&quot;<br>    }<br>}</pre><pre>apply plugin: &quot;org.owasp.dependencycheck&quot;</pre><h3>Step 2: Set Your NVD API Key in the Gradle Configuration</h3><p>There are multiple ways, but the simplest and recommended one is to set the API key as a <strong>system environment variable</strong> and then configure the plugin to pick it up.</p><h4>Option A: Pass via Environment Variable</h4><p>In your shell/CI environment:</p><pre>export NVD_API_KEY=your_actual_api_key_here</pre><p>Then in build.gradle:</p><pre>dependencyCheck {<br>    nvd.apiKey = System.getenv(&#39;NVD_API_KEY&#39;)<br>    failBuildOnCVSS = 7.0  // example: fail if CVSS &gt;= 7<br>    suppressionFile = &#39;dependency-check-suppressions.xml&#39; // optional<br>    formats = [&#39;HTML&#39;, &#39;XML&#39;]  // report formats<br>}</pre><h4>Option B: Directly Embed in build.gradle (not recommended for security reasons)</h4><pre>dependencyCheck {<br>    nvdApiKey = &#39;your_actual_api_key_here&#39;<br>}</pre><blockquote><strong><em>Warning:</em></strong><em> Avoid committing API keys directly into source code. Use environment variables or CI/CD secret management tools instead.</em></blockquote><h3>Step 3: Run Dependency-Check</h3><p>Run the scan with:</p><pre>./gradlew dependencyCheckAnalyze --info</pre><p>This will:</p><ul><li>Use your NVD API key to authenticate requests,</li><li>Speed up your CVE lookups,</li><li>Generate reports in the configured formats.</li></ul><h3>Additional Tips</h3><ul><li>In CI/CD pipelines, inject the API key as an environment variable securely.</li><li>Keep your dependency-check plugin updated for best compatibility with the NVD API.</li><li>Use failBuildOnCVSS to enforce security gates.</li></ul><h3>🔍 Final Thoughts</h3><p>Dependency-Check is your early warning system for security risks hiding in your software dependencies. But to make it effective:</p><blockquote><strong><em>Use an NVD API key. It’s free, safe, and makes your scans 5–10x faster.</em></strong></blockquote><p>Set it up today and keep your software supply chain secure — without slowing down your developers.</p><p><em>Assistant used by the Author to write this article: #ChatGPT</em></p><p><strong>Questions or tips or Blame #ChatGPT :) ?</strong></p><p>…drop them in the comments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=54d29cb04c6b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Refactoring Maven Build Scripts: Leveraging Parent POMs for Script & Code Reuse]]></title>
            <link>https://medium.com/@nagendra.raja/refactoring-maven-build-scripts-leveraging-parent-poms-for-script-code-reuse-e481d3732d8b?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/e481d3732d8b</guid>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[maven]]></category>
            <category><![CDATA[ants]]></category>
            <category><![CDATA[bazel]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Mon, 19 Jun 2023 11:35:43 GMT</pubDate>
            <atom:updated>2023-06-19T12:03:26.536Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Introduction</strong></h3><p>In the era of CI/CD (Continuous Integration/Continuous Delivery), build scripts have become crucial components of software development. However, as projects grow in complexity, build scripts can become convoluted and burdened with technical debt. To address this issue, adopting clean code practices and refactoring techniques can greatly improve the maintainability and efficiency of Maven build scripts. This article explores how to deal with Maven scripts by promoting script and source code reuse across multiple modules (maven scripts refactoring).</p><p>Maven, a popular build automation tool, simplifies and standardizes the build process by following a convention-over-configuration approach. It promotes best practices and encourages project organization and dependency management.</p><p>A Maven build script, often referred to as a POM (Project Object Model) script, is an XML file that defines the configuration and dependencies of a Maven-based project. It serves as the backbone of the project’s build process, providing instructions for compiling source code, managing dependencies, running tests, and packaging the project for distribution.</p><p>One of the key advantages of Maven is its ability to reuse build scripts across multiple projects. This reuse reduces redundancy, improves maintainability, and helps eliminate technical debt in build scripts. Technical debt refers to the accumulated work needed to fix or improve a project’s codebase and infrastructure due to suboptimal or outdated practices.</p><p>By leveraging reusable Maven build scripts, organizations can achieve the following benefits:</p><ol><li><em>Consistency</em>: Reusing a standardized build script across projects ensures consistency in the build process. Developers familiar with Maven can easily understand and work on different projects that follow the same structure and conventions.</li><li><em>Simplified Dependency Management</em>: Maven handles dependency management seamlessly. By reusing a common build script, developers can consistently specify project dependencies, ensuring that the correct versions are used across projects. This eliminates the need for manual tracking and management of dependencies, reducing the chances of version conflicts or compatibility issues.</li><li><em>Efficiency</em>: Reusing Maven build scripts saves time and effort. Instead of starting from scratch for each project, developers can leverage preconfigured build scripts that already include commonly used plugins, build profiles, and other essential configurations. This accelerates project setup and reduces the time spent on repetitive build-related tasks.</li><li><em>Standardization and Best Practices</em>: Maven promotes standardization and encourages the adoption of best practices. Reusing Maven build scripts ensures that projects follow consistent build processes, coding conventions, and quality assurance practices. This leads to cleaner code, easier collaboration among developers, and improved overall project quality.</li><li><em>Easy Maintenance and Updates</em>: When a common build script is updated to incorporate new practices, security fixes, or enhancements, all projects that reuse that script can benefit immediately. It simplifies the process of adopting improvements or addressing issues across multiple projects, reducing technical debt associated with outdated or inefficient build scripts.</li></ol><p>In Maven, there are <strong>two </strong>essential tags that enable the sharing of scripts and code across projects: &lt;packaging&gt; and &lt;packaging&gt;.</p><h3><strong><em>a) packaging</em></strong></h3><p>&lt;packaging&gt;: The &lt;packaging&gt; tag defines the type of artifact that a Maven project produces. It specifies the packaging format, such as JAR, WAR, POM, etc.</p><ol><li><em>Preparing for Inheritance of Maven Script</em></li></ol><p>The &lt;packaging&gt;pom&lt;/packaging&gt; value is used for a POM project, indicating that the project is intended to inherited by other modules so that all dependencies, configurations, variables, plugins, etc need not be repeated in the child module as they are straight coming from the parent.</p><p>Also, the packaging tag value as pom does not compile the source and test code that may exist in the same module. This way any code that needs to be shared at compile time code level dependency has to be of package type pom.</p><p><em>2. Preparing such </em><em>pom package module to share the source code</em></p><p>In order for source code that is present in cmn pom packaged module to be used by its children, make sure add this special script that share src &amp; resources directories of both main and test code.</p><pre>    &lt;build&gt;<br> &lt;plugins&gt;<br>     &lt;plugin&gt;<br>  &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;<br>  &lt;artifactId&gt;build-helper-maven-plugin&lt;/artifactId&gt;<br>  &lt;version&gt;3.0.0&lt;/version&gt;<br>  &lt;executions&gt;<br>      &lt;execution&gt;<br>   &lt;phase&gt;generate-sources&lt;/phase&gt;<br>   &lt;goals&gt;<br>       &lt;goal&gt;add-source&lt;/goal&gt;<br>   &lt;/goals&gt;<br>   &lt;configuration&gt;<br>       &lt;sources&gt;<br>    &lt;source&gt;${cmn.src}/main/java&lt;/source&gt;<br>       &lt;/sources&gt;<br>   &lt;/configuration&gt;<br>      &lt;/execution&gt;<br>      &lt;execution&gt;<br>   &lt;id&gt;add-resource&lt;/id&gt;<br>   &lt;phase&gt;generate-resources&lt;/phase&gt;<br>   &lt;goals&gt;<br>       &lt;goal&gt;add-resource&lt;/goal&gt;<br>   &lt;/goals&gt;<br>   &lt;configuration&gt;<br>       &lt;resources&gt;<br>    &lt;resource&gt;<br>        &lt;directory&gt;${cmn.src}/main/resources&lt;/directory&gt;<br>    &lt;/resource&gt;<br>       &lt;/resources&gt;<br>   &lt;/configuration&gt;<br>      &lt;/execution&gt;<br>      &lt;execution&gt;<br>   &lt;id&gt;add-test-source&lt;/id&gt;<br>   &lt;phase&gt;generate-test-sources&lt;/phase&gt;<br>   &lt;goals&gt;<br>       &lt;goal&gt;add-test-source&lt;/goal&gt;<br>   &lt;/goals&gt;<br>   &lt;configuration&gt;<br>       &lt;sources&gt;<br>    &lt;source&gt;${cmn.src}/test/java&lt;/source&gt;<br>       &lt;/sources&gt;<br>   &lt;/configuration&gt;<br>      &lt;/execution&gt;<br>      &lt;execution&gt;<br>   &lt;id&gt;add-test-resource&lt;/id&gt;<br>   &lt;phase&gt;generate-test-resources&lt;/phase&gt;<br>   &lt;goals&gt;<br>       &lt;goal&gt;add-test-resource&lt;/goal&gt;<br>   &lt;/goals&gt;<br>   &lt;configuration&gt;<br>       &lt;resources&gt;<br>    &lt;resource&gt;<br>        &lt;directory&gt;${cmn.src}/test/resources&lt;/directory&gt;<br>    &lt;/resource&gt;<br>       &lt;/resources&gt;<br>   &lt;/configuration&gt;<br>      &lt;/execution&gt;<br>  &lt;/executions&gt;<br>     &lt;/plugin&gt;<br> &lt;/plugins&gt;<br>    &lt;/build&gt;</pre><p>It configures the “build-helper-maven-plugin” plugin to handle various source and resource directories in different phases of the build process.</p><h3><strong>b) </strong>parent</h3><p>&lt;parent&gt;: The &lt;parent&gt; tag specifies the parent project from which the current project should inherit configurations. It allows a project to inherit properties, dependencies, build settings, and other configurations from a common parent project. The &lt;parent&gt; tag establishes a hierarchical relationship between projects, enabling shared configurations and reducing redundancy.</p><pre>&lt;project xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;<br>  xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd&quot;&gt;<br>    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;<br>    &lt;parent&gt;<br> &lt;groupId&gt;com.teja&lt;/groupId&gt;<br> &lt;artifactId&gt;cmn&lt;/artifactId&gt;<br> &lt;version&gt;0.0.10&lt;/version&gt;<br> &lt;relativePath&gt;../cmn/pom.xml&lt;/relativePath&gt;<br>    &lt;/parent&gt;<br>    &lt;artifactId&gt;dc&lt;/artifactId&gt;<br>    &lt;dependencies&gt;<br> &lt;dependency&gt;<br>     &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>     &lt;artifactId&gt;spring-boot-docker-compose&lt;/artifactId&gt;<br> &lt;/dependency&gt;<br>    &lt;/dependencies&gt;<br>&lt;/project&gt;</pre><p>The &lt;parent&gt; element defines the parent project from which the current project inherits its configuration.</p><ul><li>&lt;groupId&gt;: Specifies the group ID of the parent project.</li><li>&lt;artifactId&gt;: Specifies the artifact ID of the parent project.</li><li>&lt;version&gt;: Specifies the version of the parent project.</li><li>&lt;relativePath&gt;: Specifies the relative path to the parent POM file. In this case, it points to ../cmn/pom.xml.</li></ul><h3><strong>Verification</strong></h3><p>The best way to verify sanity of the relationship is go to child module directory and issue command mvn help:evaluate</p><pre>&gt;mvn help:evaluate<br></pre><p>This opens up REPL where you could type the expression ${cmn.src}</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B1hf7QiW0_wSvSLhNLHnyg.png" /></figure><p>Which in my case solves the parent property value to xxx\dctc\intg\cmn\pom.xml/../src</p><p>One could also look for depencides of this module if it includes parent script ones is by issue of command mvn dependency:tree</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TrdqUDpOPk2wl4pmyzQEFQ.png" /></figure><p>In order to see source code paths used by the child modules, run the command</p><pre>mvn clean install -X &gt; output.txt</pre><p>open the output.txt to view something like this</p><pre>[DEBUG] Source roots:<br>[DEBUG]  \dctc\intg\dc\src\main\java<br>[DEBUG]  \dctc\intg\cmn\pom.xml\..\src\main\java<br>[DEBUG]  dctc\intg\dc\target\generated-sources\annotations</pre><p>Also watch for these lines, that contain parents resources directory added</p><pre> Configuring mojo execution &#39;org.codehaus.mojo:build-helper-maven-plugin:3.0.0:add-source:default&#39; with basic configurator --&gt;<br>[DEBUG]   (f) project = MavenProject: com.teja:dc:0.0.10 @ dctc\intg\dc\pom.xml<br>[DEBUG]   (f) sources = [dctc\intg\cmn\pom.xml\..\src\main\java]<br>[DEBUG] -- end configuration --<br>[INFO] Source directory: dctc\intg\cmn\pom.xml\..\src\main\java added.<br>[INFO] <br>[INFO] --- build-helper:3.0.0:add-resource (add-resource) @ dc ---<br>[DEBUG] Loading mojo org.codehaus.mojo:build-helper-maven-plugin:3.0.0:add-resource from plugin realm ClassRealm[plugin&gt;org.codehaus.mojo:build-helper-maven-plugin:3.0.0, parent: jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a]<br>[DEBUG] Configuring mojo execution &#39;org.codehaus.mojo:build-helper-maven-plugin:3.0.0:add-resource:add-resource&#39; with basic configurator --&gt;<br>[DEBUG]   (f) project = MavenProject: com.teja:dc:0.0.10 @ dctc\intg\dc\pom.xml<br>[DEBUG]   (s) directory = dctc\intg\cmn\pom.xml/../src/main/resources<br>[DEBUG]   (f) resources = [Resource {targetPath: null, filtering: false, FileSet {directory: dctc\intg\cmn\pom.xml/../src/main/resources, PatternSet [includes: {}, excludes: {}]}}]<br>[DEBUG] -- end configuration --<br>[DEBUG] Added resource: dctc\intg\cmn\pom.xml/../src/main/resources<br>[INFO] <br>[I</pre><p>One other advantage of using the parent tag in Maven’s pom.xml file is that dependencies belonging to the same group as the parent do not need to provide a version number. The version number specified in the parent tag is automatically used for those dependencies.</p><p>For example, consider the following configuration:</p><pre>&lt;project&gt;<br>    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;<br>    &lt;groupId&gt;com.example&lt;/groupId&gt;<br>    &lt;artifactId&gt;my-project&lt;/artifactId&gt;<br>    &lt;version&gt;1.0.0&lt;/version&gt;<br>    &lt;packaging&gt;jar&lt;/packaging&gt;<br><br>    &lt;parent&gt;<br>        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;<br>        &lt;version&gt;3.1.0&lt;/version&gt;<br>        &lt;relativePath/&gt;<br>    &lt;/parent&gt;<br><br>    &lt;dependencies&gt;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>            &lt;artifactId&gt;spring-boot-docker-compose&lt;/artifactId&gt;<br>        &lt;/dependency&gt;<br>    &lt;/dependencies&gt;<br>&lt;/project&gt;</pre><p>In this example, the parent tag specifies the Spring Boot Starter Parent as the parent project, with a version number of 3.1.0. The &lt;dependencies&gt; section includes a dependency on spring-boot-docker-compose, which belongs to the same group (org.springframework.boot).</p><p>Since the parent version is defined, the version number for the spring-boot-docker-compose dependency does not need to be explicitly specified. Maven will automatically use the version defined in the parent tag (3.1.0 in this case).</p><p>This simplifies the configuration and helps ensure that all dependencies within the same group use the compatible versions defined by the parent. It also makes it easier to manage and update dependencies in the future, as you can simply update the version number in the parent tag, and all the dependencies within that group will inherit the new version.</p><h3><strong>Conclusion</strong></h3><p>By combining these two tags, Maven projects can achieve effective sharing and reuse of build script properties, configurations, and more.</p><ul><li>The &lt;packaging&gt;pom&lt;/packaging&gt; indicates that a project is focused on managing and sharing configurations rather than producing a deployable artifact.</li><li>The &lt;parent&gt; tag references the parent project that contains the shared configurations, defining the inheritance relationship and enabling the child project to inherit those configurations.</li></ul><p>With this combination, Maven projects can effectively eliminate technical debt in build scripts by centralizing and reusing common configurations, dependencies, and build logic. Changes made in the parent project automatically propagate to the child projects, promoting consistency and reducing duplication.</p><h3><strong>About</strong></h3><p>Considering the significant opportunity loss caused by the complexities and inefficiencies in your Ant, Maven, Gradle, and Bazel scripts, it is crucial to take proactive steps to address the situation. Tejasoft, with its extensive experience of over 20 years and expertise in dealing with 2.3 billion lines of code across various technology stacks, offers an innovative solution to fix the code mess. We are the pioneers in applying a unique Code Factory work style, capable of transforming dirty tech debt-ridden code into clean, maintainable code with remarkable automation.</p><p>By engaging Tejasoft’s services, you can leverage our deep knowledge and automated code transformation capabilities to alleviate the burden of technical debt and unlock the true potential of your products in any tech stack. With a proven track record and commitment to delivering clean code outputs, TejaSoft stands as the leading Code Doctor system in the industry.</p><p>Don’t let your legacy code hold you back from realizing your business goals. Contact TejaSoft today to explore how their innovative solutions can revolutionize your development process and drive your product for scalable revenues in spite of #techdebt endless pains, stress &amp; team execuses.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e481d3732d8b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Exploring Gradle Kotlin DSL: Managing Directories, Objects, Properties, and Tasks]]></title>
            <link>https://medium.com/@nagendra.raja/exploring-gradle-kotlin-dsl-managing-directories-objects-properties-and-tasks-eae3ce81d1e6?source=rss-efd6e4a8f998------2</link>
            <guid isPermaLink="false">https://medium.com/p/eae3ce81d1e6</guid>
            <category><![CDATA[property]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[sourcesets]]></category>
            <dc:creator><![CDATA[Raja Nagendra Kumar, Code Doctor/Innovator]]></dc:creator>
            <pubDate>Sat, 10 Jun 2023 15:56:49 GMT</pubDate>
            <atom:updated>2023-06-11T05:00:14.348Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f6WP7qAtSB-_cqldN_tdgg.png" /></figure><h3><strong>Introduction</strong></h3><p>In Gradle, the build automation tool, the Kotlin DSL (Domain-Specific Language) provides a powerful and expressive way to configure and customize build scripts. In this article, we will explore default directory pointing properties and objects in Gradle Kotlin DSL. We will learn how to set and read these properties, define custom properties, and work with different variations of source sets, such as main, java, test, resources, and Kotlin language.</p><p><em>Default Directory Pointing Properties:</em> Gradle provides several default directory-pointing properties. Let’s discuss each property, its description, and how to set and read them.</p><ul><li>buildDir: Points to the build directory where Gradle places the compiled code, resources, and other build-related files.</li><li>buildFile: Represents the build script file (e.g., build.gradle.kts) being executed.</li><li>docsDir: Refers to the directory where the generated documentation is placed.</li><li>projectDir: Points to the root directory of the project.</li><li>reportsDir: Represents the directory where generated reports are stored.</li><li>rootDir: Points to the root directory of the Gradle project.</li><li>testReportDir: Refers to the directory where test reports are generated.</li><li>testResultsDir: Represents the directory where test results are stored.</li><li>layout.buildDirectory property represents the directory where build outputs, such as compiled classes, generated resources, and other build artifacts, are placed. The layout.buildDirectory property is useful when you need to reference or manipulate build artifacts within your build script or tasks.</li></ul><p>You can access these properties directly within your Gradle Kotlin build script. For example:</p><pre>println(&quot;buildDir: ${buildDir.absolutePath}&quot;)<br>println(&quot;buildFile: ${buildFile.absolutePath}&quot;)<br>println(&quot;projectDir: ${projectDir.absolutePath}&quot;)<br>println(&quot;rootDir: ${rootDir.absolutePath}&quot;)<br></pre><p>We can also read properties that are part of project object using project.properties[&quot;propertyName&quot;] syntax. This approach makes all plugins defined properties are accessible.</p><p>Here&#39;s an example of reading the testReportDir property from property map</p><pre>println(&quot;testReportDir:&quot; + project.properties[&quot;testReportDir&quot;])<br>println(&quot;testResultsDir:&quot; + project.properties[&quot;testResultsDir&quot;])<br>println(&quot;docsDir:&quot; + project.properties[&quot;docsDir&quot;])<br>println(&quot;reportsDir:&quot; + project.properties[&quot;reportsDir&quot;])</pre><p>To access the value of the layout.buildDirectory property in Gradle Kotlin DSL, you can use the following code</p><pre>println(&quot;Build Directory: ${layout.buildDirectory.get()}&quot;)<br>println(&quot;Project Directory: ${project.layout.projectDirectory}&quot;)<br>println(&quot;ProjectDirectory: ${layout.projectDirectory}&quot;)</pre><p>From the above code, we note project is the assumed parent object i.e. layout is a child property of the project object, that can be accessed as project.layout or just as layout.</p><p>We can also attach our own custom properties to projectobject this way</p><pre>open class MyExtension {<br>    var myProperty: String = &quot;&quot;<br>}<br><br>val myExtension = extensions.create(&quot;myExtension&quot;, MyExtension::class)<br><br>configure&lt;MyExtension&gt; {<br>    myProperty = &quot;configure&quot;<br>}<br><br>myExtension.myProperty = &quot;property&quot;<br><br>task(&quot;myTask&quot;) {<br>    doLast {<br>         println(&quot;My Property: ${myExtension.myProperty}&quot;)<br>    }<br>}</pre><p><em>Default Objects and Source Sets: </em>Gradle provides default objects that allow you to work with source sets, tasks, configurations, and dependencies.</p><p>core objects in Gradle:</p><p>Project:</p><ul><li>Access: The Project object is automatically available in Gradle build scripts without any explicit declaration.</li><li>Explanation: The Project object represents the entire build or project. It provides a wide range of methods and properties to configure and interact with the build environment. You can access and configure the project properties, apply plugins, define tasks, create configurations, and more using the Project object.</li></ul><p>Plugins:</p><ul><li>Access: The pluginsobject is automatically available in Gradle build scripts without any explicit declaration.</li><li>Explanation: The plugins object in Gradle provides methods to manage plugins in your build script. You can apply plugins using the plugins block or by using the apply method. The plugins object also offers functions to search for and resolve plugin dependencies, making it straightforward to incorporate third-party plugins into your build.</li><li>Plugins in Gradle provide a powerful mechanism for extending the build configuration and adding additional functionality to your Gradle projects. A plugin is a reusable piece of build logic that encapsulates tasks, configurations, and other build-related elements.</li><li>By applying a plugin to your Gradle project, you can easily incorporate features such as code quality checks, dependency management, test frameworks, deployment automation, and more. Plugins enable you to define custom tasks, configure default behaviors, and integrate with external tools or libraries.</li><li>Plugins can be sourced from various locations, including the Gradle Plugin Portal, custom repositories, or locally defined scripts. They can be applied at the project level or for specific subprojects, allowing flexibility in how you structure and configure your builds.</li><li>Plugins greatly enhance the reusability, modularity, and maintainability of your Gradle builds. They enable you to leverage existing solutions and share build configurations across projects, promoting consistency and efficiency in your development workflows.</li></ul><p>Task:</p><ul><li>Access: Tasks are defined using the tasks property of the Project object, e.g., tasks.register(&quot;myTask&quot;) { ... }.</li><li>Explanation: The Task object represents a single atomic piece of work within the build. You can define tasks to perform specific actions, such as compiling code, running tests, generating documentation, etc. Tasks can have dependencies on other tasks, and you can configure their behavior using properties and actions.</li></ul><p>Configuration:</p><ul><li>Access: Configurations are defined using the configurations property of the Project object, e.g., configurations.create(&quot;myConfig&quot;) { ... }.</li><li>Explanation: The Configuration object represents a logical grouping of dependencies. You can define configurations to specify the dependencies required for building and running the project. Configurations define the external modules or libraries that your project depends on and are used to resolve and download the required artifacts.</li></ul><p>Dependency:</p><ul><li>Access: Dependencies are typically defined within configurations, e.g., dependencies { implementation &quot;com.example:library:1.0&quot; }.</li><li>Explanation: The Dependency object represents a dependency on an external module or library. Dependencies are declared within configurations and specify the external artifacts required for the project. Dependencies can be declared with specific versions and can be resolved from repositories.</li></ul><p>SourceSet:</p><ul><li>Access: Source sets are typically defined within the sourceSets block of the Project object, e.g., sourceSets { main { ... } }.</li><li>Explanation: The SourceSet object represents a group of source files for a specific purpose, such as the main source code, test code, or additional source sets. Source sets allow you to organize and configure different types of source files in your project, such as Java, Kotlin, resources, etc.</li></ul><p>gradle:</p><p>The gradle object in Gradle represents the Gradle runtime and provides access to various properties, methods, and configurations related to the current Gradle build.</p><p>Here are some common use cases and functionalities of the gradle object:</p><ol><li>Accessing Gradle properties: You can access properties defined in the Gradle build script or passed via the command line using the gradle.properties file. For example, gradle.propertyName retrieves the value of the property named propertyName.</li><li>Configuring build behavior: The gradle object allows you to configure various aspects of the build process. For instance, you can define build-wide options such as task timeouts, parallel execution, or build caching.</li><li>Working with the build environment: The gradle object provides information about the build environment, including the Gradle version, the project directory, and the build directory. For example, gradle.gradleVersion retrieves the current Gradle version being used.</li><li>Accessing the project hierarchy: You can access the root project and subprojects within a multi-project build using the gradle.rootProject and gradle.allprojects properties, respectively. This allows you to perform operations on the entire project hierarchy.</li><li>Executing tasks dynamically: The gradle object provides methods to execute tasks programmatically. For example, you can use gradle.task(taskName).execute() to execute a specific task by its name.</li><li>Registering custom logic: You can register custom logic or actions to be executed during the build lifecycle using the gradle.buildFinished or gradle.taskGraph.whenReady properties. This allows you to add custom behavior after the build is finished or before tasks are executed.</li><li>Logging and reporting: The gradle object offers a logging facility through its gradle.logger property. You can log messages at different levels (e.g., info, debug, warn, error) to provide feedback and track the progress of the build.</li></ol><p>These are just a few examples of the capabilities provided by the gradle object. It serves as a central entry point to interact with the Gradle runtime and enables you to customize the build process according to your requirements.</p><p>Let’s explore some of these objects and their usage.</p><p><em>Source Sets</em></p><ul><li>sourceSets.main: Represents the main source set, which includes the main Java source code, resources, and Kotlin source code.</li><li>sourceSets.test: Represents the test source set, which includes test Java source code and resources.</li><li>sourceSets.getByName(&quot;customSet&quot;): Represents a custom source set named &quot;customSet&quot; that you can define and configure according to your project&#39;s needs.</li><li>sourceSets.getByName(&quot;customTest&quot;): Represents a custom source set named &quot;customTest&quot; for test-related code.</li></ul><p>You can customize these source sets by adding or modifying the source directories. For example:</p><pre>sourceSets {<br>    getByName(&quot;main&quot;) {<br>        java.srcDirs(<br>            &quot;src/main/java&quot;,<br>            &quot;src/main/otherJava&quot;<br>        )<br>        resources.srcDirs(<br>            &quot;src/main/resources&quot;,<br>            &quot;src/main/otherResources&quot;<br>        )<br>        kotlin.srcDir(&quot;src/main/kotlin&quot;)<br>    }<br>    getByName(&quot;test&quot;) {<br>        java.srcDir(&quot;src/test/java&quot;)<br>        resources.srcDir(&quot;src/test/resources&quot;)<br>    }<br>    <br>    create(&quot;customSet&quot;) {<br>        java.srcDir(&quot;src/customSet/java&quot;)<br>        resources.srcDir(&quot;src/customSet/resources&quot;)<br>    }<br>    <br>    create(&quot;customTest&quot;) {<br>        java.srcDir(&quot;src/customTest/java&quot;)<br>        resources.srcDir(&quot;src/customTest/resources&quot;)<br>    }<br>}</pre><p>The way to read these directories</p><pre>// Reading and printing Java source directories<br>println(&quot;Main Java Source Directories: ${sourceSets.main.get().java.srcDirs}&quot;)<br>println(&quot;Test Java Source Directories: ${sourceSets.test.get().java.srcDirs}&quot;)<br><br>// Reading and printing Kotlin source directories<br>println(&quot;Main Kotlin Source Directories: ${sourceSets.main.get().kotlin.srcDirs}&quot;)<br>println(&quot;Test Kotlin Source Directories: ${sourceSets.test.get().kotlin.srcDirs}&quot;)<br><br>// Reading and printing custom source directories<br>println(&quot;Custom Set Java Source Directories: ${sourceSets.getByName(&quot;customSet&quot;).java.srcDirs}&quot;)<br>println(&quot;Custom Set Resources Directories: ${sourceSets.getByName(&quot;customSet&quot;).resources.srcDirs}&quot;)<br>println(&quot;Custom Test Java Source Directories: ${sourceSets.getByName(&quot;customTest&quot;).java.srcDirs}&quot;)<br>println(&quot;Custom Test Resources Directories: ${sourceSets.getByName(&quot;customTest&quot;).resources.srcDirs}&quot;)</pre><p>Accessing Other Core Objects: You can access other core objects like Project, Task, Configuration, Dependency, Plugin, File, Logger, ConfigurationContainer, and TaskContainer using the respective methods and properties. For example:</p><pre>val project: Project = project<br>println(&quot;Project: $project&quot;)<br><br>val task: Task = tasks.getByName(&quot;cp&quot;)<br>println(&quot;Task: $task&quot;)<br><br>val configuration: Configuration = configurations.create(&quot;myConfig&quot;)<br>println(&quot;Configuration: $configuration&quot;)<br><br>val dependency: Dependency = dependencies.create(&quot;com.example:library:1.0&quot;)<br>println(&quot;Dependency: $dependency&quot;)<br><br>val sourceSet: SourceSet = sourceSets.getByName(&quot;main&quot;)<br>println(&quot;SourceSet: $sourceSet&quot;)<br><br>val plugin = plugins.getPlugin(&quot;java&quot;)<br>println(&quot;Plugin: $plugin&quot;)<br><br>val file: File = file(&quot;path/to/file.txt&quot;)<br>println(&quot;File: $file&quot;)<br><br>logger.info(&quot;This is an informational message.&quot;)<br><br>val configurationContainer: ConfigurationContainer = configurations<br>println(&quot;ConfigurationContainer: $configurationContainer&quot;)<br><br>val taskContainer: TaskContainer = tasks<br>println(&quot;TaskContainer: $taskContainer&quot;)</pre><p>gradle</p><p>Sample usage of gradleobject</p><pre>tasks.register(&quot;customTask&quot;) {<br> doLast {<br>  // Accessing Gradle properties<br>  val myProperty = project.properties[&quot;myProperty&quot;]<br>  println(&quot;Value of myProperty: $myProperty&quot;)<br><br>  // Working with the build environment<br>  val gradleVersion = gradle.gradleVersion<br>  val projectDir = project.projectDir<br>  val buildDir = project.buildDir<br>  println(&quot;Gradle version: $gradleVersion&quot;)<br>  println(&quot;Project directory: $projectDir&quot;)<br>  println(&quot;Build directory: $buildDir&quot;)<br><br>  // Accessing the project hierarchy<br>  val rootProject = project.rootProject<br>  val allProjects = project.allprojects<br>  println(&quot;Root project name: ${rootProject.name}&quot;)<br>  println(&quot;All project names:&quot;)<br>  allProjects.forEach { println(it.name) }<br><br>  // Registering custom logic<br>  gradle.buildFinished {<br>   println(&quot;Build finished!&quot;)<br>  }<br><br>  gradle.taskGraph.whenReady {<br>   println(&quot;Tasks in the task graph:&quot;)<br>   allTasks.forEach { println(path) }<br>  }<br><br>  // Logging and reporting<br>  logger.info(&quot;This is an informational message.&quot;)<br>  logger.warn(&quot;This is a warning message.&quot;)<br> }<br>}</pre><p><em>Printing Dependencies and Tasks</em></p><p>You can print the dependencies and tasks within your Gradle script using the following code snippets</p><pre>tasks.register(&quot;printDependencies&quot;) {<br>    doLast {<br>        println(&quot;Dependencies:&quot;)<br>        configurations.forEach { configuration -&gt;<br>            configuration.dependencies.forEach { dependency -&gt;<br>                println(&quot;${configuration.name} -&gt; ${dependency.name}&quot;)<br>            }<br>        }<br>    }<br>}<br><br>tasks.register(&quot;printTasks&quot;) {<br>    doLast {<br>        println(&quot;Tasks:&quot;)<br>        gradle.taskGraph.allTasks.forEach { task -&gt;<br>            println(task.path)<br>        }<br>    }<br>}</pre><p>There is a special propertydefaultTasks property allows you to specify a list of tasks that should be executed by default when running the build without specifying any task names.</p><p>The defaultTasks property is typically defined in the build script (e.g., build.gradle or build.gradle.kts) and accepts a list of task names as its value. For example, if you want to set task1 and task2 as the default tasks, you can do the following</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f6WP7qAtSB-_cqldN_tdgg.png" /></figure><pre>defaultTasks(&quot;task1&quot;, &quot;task2&quot;)</pre><p>When you run the build without specifying any task names, Gradle will automatically execute the tasks specified in defaultTasks in the order they are defined. For example, running gradle or ./gradlew in the command line will execute task1 and task2 by default.</p><p>It’s important to note that defaultTasks does not prevent you from running other tasks explicitly by specifying their names in the command line. You can still execute other tasks alongside the default tasks.</p><p>You can also override the default tasks by providing task names explicitly in the command line. For example, running gradle task3 will execute task3 instead of the default tasks.</p><p>Defining default tasks can be useful to define a common set of tasks that should be executed when someone runs the build without specifying specific task names. It provides a convenient way to specify the entry point for your build process.</p><h3>Conclusion</h3><p>In this article, we explored default directory pointing properties and objects in Gradle Kotlin DSL. We learned how to set and read these properties, define custom properties, and work with different variations of source sets. With this knowledge, you can effectively configure and customize your Gradle build scripts to meet your requirements.</p><p>Note: You will have to use pluginkotlin(“jvm”) version “1.8.22”to make Kotlin part of the <em>sourceSets</em>to work</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eae3ce81d1e6" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>