<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ed Legaspi</title>
    <description>The latest articles on DEV Community by Ed Legaspi (@czetsuya).</description>
    <link>https://dev.to/czetsuya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F918356%2F3bd969db-a70a-492b-bad3-72081965f313.jpg</url>
      <title>DEV Community: Ed Legaspi</title>
      <link>https://dev.to/czetsuya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/czetsuya"/>
    <language>en</language>
    <item>
      <title>Stop Rebuilding Exception Handling in Every Spring Boot Project</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Mon, 06 Jul 2026 13:20:38 +0000</pubDate>
      <link>https://dev.to/czetsuya/stop-rebuilding-exception-handling-in-every-spring-boot-project-24hc</link>
      <guid>https://dev.to/czetsuya/stop-rebuilding-exception-handling-in-every-spring-boot-project-24hc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnvmlkgvde90cz3915du.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnvmlkgvde90cz3915du.png" alt="Discover nerv-exception" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
After working on multiple Spring Boot projects over the years, I noticed an interesting pattern.&lt;/p&gt;

&lt;p&gt;Every application had different business requirements.&lt;/p&gt;

&lt;p&gt;But almost every application ended up with the same exception handling infrastructure.&lt;/p&gt;

&lt;p&gt;I found myself recreating the same classes over and over again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Custom exceptions&lt;/li&gt;
&lt;li&gt;Error response models&lt;/li&gt;
&lt;li&gt;Error codes&lt;/li&gt;
&lt;li&gt;Validation handlers&lt;/li&gt;
&lt;li&gt;Feign error decoders&lt;/li&gt;
&lt;li&gt;Utility classes&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The business logic changed.&lt;/p&gt;

&lt;p&gt;The exception handling didn't.&lt;/p&gt;

&lt;p&gt;Eventually I asked myself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why am I rebuilding this for every project?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question became &lt;strong&gt;nerv-exception&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of copying hundreds of lines of exception handling infrastructure from project to project, I wanted a reusable, production-ready library that could become the foundation for every Spring Boot application.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A typical Spring Boot application usually starts with something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalExceptionHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"USER_NOT_FOUND"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodArgumentNotValidException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodArgumentNotValidException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks simple.&lt;/p&gt;

&lt;p&gt;Until the application grows.&lt;/p&gt;

&lt;p&gt;Now you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;business exceptions&lt;/li&gt;
&lt;li&gt;validation exceptions&lt;/li&gt;
&lt;li&gt;security exceptions&lt;/li&gt;
&lt;li&gt;Feign client exceptions&lt;/li&gt;
&lt;li&gt;Kafka consumer exceptions&lt;/li&gt;
&lt;li&gt;asynchronous processing&lt;/li&gt;
&lt;li&gt;scheduled jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every new integration introduces another place where exceptions need to be translated.&lt;/p&gt;

&lt;p&gt;Soon enough, different parts of your application return completely different error payloads.&lt;/p&gt;

&lt;p&gt;Your REST API becomes inconsistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Wanted
&lt;/h2&gt;

&lt;p&gt;I wasn't trying to replace Spring Boot.&lt;/p&gt;

&lt;p&gt;Spring Boot already provides excellent exception handling capabilities.&lt;/p&gt;

&lt;p&gt;Instead, I wanted a reusable foundation that provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consistent API responses&lt;/li&gt;
&lt;li&gt;centralized error codes&lt;/li&gt;
&lt;li&gt;minimal boilerplate&lt;/li&gt;
&lt;li&gt;RFC 7807 Problem Details&lt;/li&gt;
&lt;li&gt;Spring Boot auto-configuration&lt;/li&gt;
&lt;li&gt;modular architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly...&lt;/p&gt;

&lt;p&gt;I wanted something &lt;strong&gt;I would actually use in production.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing nerv-exception
&lt;/h2&gt;

&lt;p&gt;The library is intentionally split into multiple modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nerv-exception-api
nerv-exception-core
nerv-exception-spring-web
nerv-exception-spring-boot-starter
nerv-exception-spring-feign
nerv-exception-event
nerv-exception-spring-kafka
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module has a single responsibility.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;api&lt;/strong&gt; contains only the public contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;core&lt;/strong&gt; contains the domain exception model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;spring-web&lt;/strong&gt; integrates with Spring MVC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;starter&lt;/strong&gt; provides auto-configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feign&lt;/strong&gt; handles remote service errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;event&lt;/strong&gt; standardizes exception events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;spring-kafka&lt;/strong&gt; integrates with Kafka consumers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applications only depend on what they actually need.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding the Library
&lt;/h2&gt;

&lt;p&gt;Getting started is intentionally simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.czetsuyatech&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;nerv-exception-spring-boot-starter&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;Spring Boot auto-configuration registers everything automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Standardized Error Responses
&lt;/h2&gt;

&lt;p&gt;One of the biggest goals of the project was consistency.&lt;/p&gt;

&lt;p&gt;Instead of every controller returning its own custom JSON, APIs can return standardized RFC 7807 Problem Details.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/problems/user-not-found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User 123 was not found."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes APIs significantly easier to consume.&lt;/p&gt;

&lt;p&gt;Frontend developers know exactly what to expect.&lt;/p&gt;

&lt;p&gt;API documentation becomes simpler.&lt;/p&gt;

&lt;p&gt;Clients become easier to implement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designed for Extension
&lt;/h2&gt;

&lt;p&gt;One design decision I made very early was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid forcing applications into one way of doing things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The library provides sensible defaults while remaining extensible.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom error codes&lt;/li&gt;
&lt;li&gt;custom exception mapping&lt;/li&gt;
&lt;li&gt;trace context propagation&lt;/li&gt;
&lt;li&gt;Feign integration&lt;/li&gt;
&lt;li&gt;Kafka integration&lt;/li&gt;
&lt;li&gt;event publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applications can override behavior without modifying the library itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Runnable Examples
&lt;/h2&gt;

&lt;p&gt;Documentation is helpful.&lt;/p&gt;

&lt;p&gt;Runnable code is even better.&lt;/p&gt;

&lt;p&gt;That's why I also created a companion repository containing complete Spring Boot applications demonstrating how to use the library in real projects.&lt;/p&gt;

&lt;p&gt;Rather than reading isolated snippets, developers can clone the repository, run the application, and explore the implementation themselves.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Open Source?
&lt;/h2&gt;

&lt;p&gt;Throughout my career I've benefited enormously from open source.&lt;/p&gt;

&lt;p&gt;Many libraries have saved me countless hours.&lt;/p&gt;

&lt;p&gt;Building &lt;strong&gt;nerv-exception&lt;/strong&gt; is my opportunity to give something back.&lt;/p&gt;

&lt;p&gt;If another developer no longer has to rebuild the same exception handling infrastructure for their next project, then this library has already accomplished its goal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/czetsuyatech/nerv-exception" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-exception&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Maven Central
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.czetsuyatech&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;nerv-exception-spring-boot-starter&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Projects
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm continuously improving the library based on real-world experience.&lt;/p&gt;

&lt;p&gt;If you have ideas, suggestions, or find it useful, I'd love to hear your feedback.&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>opensource</category>
      <category>api</category>
    </item>
    <item>
      <title>Stop Rewriting CI/CD: Reusable GitHub Actions for Maven Projects</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Tue, 28 Apr 2026 00:36:48 +0000</pubDate>
      <link>https://dev.to/czetsuya/stop-rewriting-cicd-reusable-github-actions-for-maven-projects-1bp1</link>
      <guid>https://dev.to/czetsuya/stop-rewriting-cicd-reusable-github-actions-for-maven-projects-1bp1</guid>
      <description>&lt;p&gt;If you’ve worked on multiple Java projects, you’ve probably run into this:&lt;/p&gt;

&lt;p&gt;Every repository has its own version of a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;Slightly different YAML.&lt;br&gt;&lt;br&gt;
Slightly different setup steps.&lt;br&gt;&lt;br&gt;
Slightly different ways of doing the same thing.&lt;/p&gt;

&lt;p&gt;And somehow… none of them are reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  😤 The Real Problem
&lt;/h2&gt;

&lt;p&gt;It’s not that CI/CD is hard.&lt;/p&gt;

&lt;p&gt;It’s that it’s &lt;strong&gt;repetitive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You end up doing the same things over and over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up Java
&lt;/li&gt;
&lt;li&gt;Cache Maven dependencies
&lt;/li&gt;
&lt;li&gt;Run tests
&lt;/li&gt;
&lt;li&gt;Build artifacts
&lt;/li&gt;
&lt;li&gt;Handle versioning
&lt;/li&gt;
&lt;li&gt;Publish or release
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then copy that workflow into another repo…&lt;br&gt;&lt;br&gt;
…and tweak it just enough to break consistency.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of problem automation should solve — yet we keep duplicating it.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 A Different Approach
&lt;/h2&gt;

&lt;p&gt;Instead of treating each pipeline as a one-off YAML file, I started thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if CI/CD steps were modular and reusable — just like code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s where &lt;strong&gt;NERV-Actions&lt;/strong&gt; came from.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/czetsuyatech/nerv-actions" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-actions&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ What NERV-Actions Is
&lt;/h2&gt;

&lt;p&gt;NERV-Actions is a collection of &lt;strong&gt;reusable GitHub Actions designed for Maven-based projects&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Break CI/CD into small, composable actions
&lt;/li&gt;
&lt;li&gt;Reuse them across repositories
&lt;/li&gt;
&lt;li&gt;Keep pipelines consistent without rewriting everything
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# repeated in every repo
- uses: actions/setup-java@v4
- run: mvn clean install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You plug in reusable building blocks that already handle those concerns.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 Why This Matters
&lt;/h2&gt;

&lt;p&gt;Reusable workflows aren’t just about convenience — they solve real scaling problems.&lt;/p&gt;

&lt;p&gt;When you define things once and reuse them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You reduce duplication
&lt;/li&gt;
&lt;li&gt;You avoid inconsistencies across repos
&lt;/li&gt;
&lt;li&gt;You make updates easier (change once, apply everywhere)
&lt;/li&gt;
&lt;li&gt;You spend less time debugging YAML
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Design Philosophy
&lt;/h2&gt;

&lt;p&gt;I didn’t try to build a “one-size-fits-all” pipeline.&lt;/p&gt;

&lt;p&gt;Instead, NERV-Actions focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Composable units&lt;/strong&gt; → mix only what you need
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensible defaults&lt;/strong&gt; → minimal setup required
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; → same patterns across projects
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt; → override when necessary
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s closer to building blocks than a framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Example Use Case
&lt;/h2&gt;

&lt;p&gt;For a typical Maven project, your pipeline usually boils down to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run tests
&lt;/li&gt;
&lt;li&gt;Build artifacts
&lt;/li&gt;
&lt;li&gt;Publish packages
&lt;/li&gt;
&lt;li&gt;Create releases
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With reusable actions, you don’t redefine these steps every time—you just assemble them.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 Why Not Just Copy-Paste?
&lt;/h2&gt;

&lt;p&gt;Because copy-paste doesn’t scale.&lt;/p&gt;

&lt;p&gt;It works for 2–3 repos.&lt;br&gt;&lt;br&gt;
It breaks down at 10+.  &lt;/p&gt;

&lt;p&gt;Suddenly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixes don’t propagate
&lt;/li&gt;
&lt;li&gt;Pipelines drift
&lt;/li&gt;
&lt;li&gt;Debugging becomes inconsistent
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reusable actions solve that by centralizing the logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Still Evolving
&lt;/h2&gt;

&lt;p&gt;This isn’t meant to be a “perfect CI/CD solution.”&lt;/p&gt;

&lt;p&gt;It’s a practical attempt to reduce the friction I kept running into across projects.&lt;/p&gt;

&lt;p&gt;If you’re dealing with the same repetition in GitHub Actions, this might be useful:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/your-repo-link" rel="noopener noreferrer"&gt;https://github.com/your-repo-link&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💬 Curious About Your Setup
&lt;/h2&gt;

&lt;p&gt;Are you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;copying workflows between repos?
&lt;/li&gt;
&lt;li&gt;using reusable workflows heavily?
&lt;/li&gt;
&lt;li&gt;or building your own internal action libraries?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would be interesting to hear how others are handling this.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>cicd</category>
      <category>java</category>
    </item>
    <item>
      <title>I Got Tired of Rewriting Audit Logs in Spring Boot — So I Built nerv-audit</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Fri, 17 Apr 2026 07:18:23 +0000</pubDate>
      <link>https://dev.to/czetsuya/i-got-tired-of-rewriting-audit-logs-in-spring-boot-so-i-built-nerv-audit-3cg4</link>
      <guid>https://dev.to/czetsuya/i-got-tired-of-rewriting-audit-logs-in-spring-boot-so-i-built-nerv-audit-3cg4</guid>
      <description>&lt;p&gt;Every backend system eventually hits this moment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who changed this record?”&lt;br&gt;
“What was the previous value?”&lt;br&gt;
“When did it happen?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simple questions… until you actually need answers in production.&lt;/p&gt;




&lt;p&gt;⚠️ Note: The core module is commercial, but you can explore the public modules and examples here:&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In most of my Spring Boot projects, I relied on Hibernate Envers.&lt;/p&gt;

&lt;p&gt;It works—but in real systems, it starts to hurt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You repeat the same setup across services&lt;/li&gt;
&lt;li&gt;Audit queries are hard to read and maintain&lt;/li&gt;
&lt;li&gt;Business-level audit logic gets scattered&lt;/li&gt;
&lt;li&gt;Small mistakes become painful in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a few projects, I realized:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wasn’t building features anymore—I was rebuilding audit infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  What I Actually Wanted
&lt;/h2&gt;

&lt;p&gt;Not a replacement for Envers.&lt;/p&gt;

&lt;p&gt;Just something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standardizes audit handling&lt;/li&gt;
&lt;li&gt;Reduces boilerplate&lt;/li&gt;
&lt;li&gt;Makes queries readable&lt;/li&gt;
&lt;li&gt;Works consistently across projects&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  So I Built nerv-audit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;nerv-audit&lt;/a&gt; is a lightweight layer on top of Envers that focuses on &lt;strong&gt;developer experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of wiring everything manually, you get a cleaner way to work with audit data.&lt;/p&gt;


&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Without nerv-audit
&lt;/h3&gt;

&lt;p&gt;You end up dealing with low-level Envers APIs and custom query logic.&lt;/p&gt;
&lt;h3&gt;
  
  
  With nerv-audit
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVerticalAudits&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That’s it.&lt;/p&gt;



&lt;p&gt;Want to see how it's implemented in a real project?&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What It Improves
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Consistent Audit Handling
&lt;/h3&gt;

&lt;p&gt;Define audit behavior once, reuse everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@AuditedEntity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Cleaner Queries
&lt;/h3&gt;

&lt;p&gt;No more complex Envers query construction.&lt;/p&gt;

&lt;p&gt;You focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What changed&lt;/li&gt;
&lt;li&gt;When it changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not &lt;em&gt;how&lt;/em&gt; to retrieve it.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Less Repetition
&lt;/h3&gt;

&lt;p&gt;Across projects, the pattern is always the same.&lt;/p&gt;

&lt;p&gt;nerv-audit abstracts that pattern so you don’t rewrite it every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;This came from real production work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple systems&lt;/li&gt;
&lt;li&gt;Repeated audit requirements&lt;/li&gt;
&lt;li&gt;Real incidents where audit data mattered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point, it made more sense to &lt;strong&gt;abstract the solution&lt;/strong&gt; than keep rebuilding it.&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Model (Transparency)
&lt;/h2&gt;

&lt;p&gt;nerv-audit is &lt;strong&gt;not fully open-source&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a &lt;strong&gt;free tier&lt;/strong&gt; you can use&lt;/li&gt;
&lt;li&gt;Some advanced capabilities are part of a &lt;strong&gt;paid core&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Note: The core module is commercial, but you can explore the public modules and examples here:&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose this approach to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the project sustainable&lt;/li&gt;
&lt;li&gt;Continue improving it over time&lt;/li&gt;
&lt;li&gt;Focus on real-world use cases&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;p&gt;This might be useful if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build Spring Boot applications&lt;/li&gt;
&lt;li&gt;Use Hibernate Envers (or plan to)&lt;/li&gt;
&lt;li&gt;Want a cleaner way to handle audit logs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  I’d Like Your Input
&lt;/h2&gt;

&lt;p&gt;How are you handling audit logs today?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pure Envers?&lt;/li&gt;
&lt;li&gt;Custom implementation?&lt;/li&gt;
&lt;li&gt;Something else?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m especially interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pain points&lt;/li&gt;
&lt;li&gt;Query challenges&lt;/li&gt;
&lt;li&gt;Scaling issues&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're working with Envers, try it out and let me know what breaks—or what works better.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>hibernate</category>
      <category>backend</category>
    </item>
    <item>
      <title>Hands-On Coding: Exploring Hyperparameters for Programmers</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Thu, 07 Mar 2024 04:54:31 +0000</pubDate>
      <link>https://dev.to/czetsuya/hands-on-coding-exploring-hyperparameters-for-programmers-5c9c</link>
      <guid>https://dev.to/czetsuya/hands-on-coding-exploring-hyperparameters-for-programmers-5c9c</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In this article, we will explore different techniques for finding the optimal hyperparameter values from a given set of parameters in a grid. Particularly we will look at RandomizedSearchCV, GridSearchCV, and BayesSearchCV.&lt;/p&gt;

&lt;p&gt;In this blog you will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to initialize the parameter grid.&lt;/li&gt;
&lt;li&gt;How to find the optimal hyperparameters based on a given technique.&lt;/li&gt;
&lt;li&gt;How to build a model (XGBClassifier) to use the hyperparameters.&lt;/li&gt;
&lt;li&gt;How to score the performance of the model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  RandomizedSearchCV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_grid = {
    "gamma": [0, 0.1, 0.2, 0.5, 1, 1.5, 2, 3, 6, 12, 20],
    "learning_rate": [0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8],
    "max_depth": [1, 2, 3, 4, 5, 6, 8, 12],
    "n_estimators": [25, 50, 65, 80, 100, 115, 200]
}

grid_search = RandomizedSearchCV(estimator=classifier_0, param_distributions=param_grid, scoring=scoring)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GridSearchCV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_grid = {
    "gamma": [0, 0.1, 0.2, 0.5, 1, 1.5, 2, 3, 6, 12, 20],
    "learning_rate": [0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8],
    "max_depth": [2, 3, 4, 5, 6, 8, 12],
    "n_estimators": [25, 50, 65, 80, 100, 115, 200]
}

grid_search = GridSearchCV(estimator=classifier_0, param_grid=param_grid, scoring=scoring)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  BayesSearchCV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_bayes = {
    'gamma': Categorical(param_grid['gamma']),
    'learning_rate': Categorical(param_grid['learning_rate']),
    'max_depth': Categorical(param_grid['max_depth']),
    'n_estimators': Categorical(param_grid['n_estimators'])
}

grid_search = BayesSearchCV(estimator=classifier_0, search_spaces=param_bayes, scoring=scoring, n_jobs=-1, cv=10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Finding the Best HyperParameters
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;best_model = grid_search.fit(X_train, y_train)
hyperparams = best_model.best_params_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Building and Scoring the Classifier using the HyperParameters
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Fitting the Model
ne = hyperparams['n_estimators']
lr = hyperparams['learning_rate']
md = hyperparams['max_depth']
gm = hyperparams['gamma']
print("Recommended Params &amp;gt;&amp;gt;", f"ne: {ne},", f"lr: {lr}", f"md: {md}", f"gm: {gm}")

# Build Classification Model
classifier_1 = XGBClassifier(
    base_score=0.5,
    colsample_bylevel=1,
    colsample_bynode=1,
    objective=objective,
    booster="gbtree",
    eval_metric=eval_metric_list,
    n_estimators=ne,
    learning_rate=lr,
    max_depth=md,
    gamma=gm,
    subsample=0.8,
    colsample_bytree=1,
    random_state=1
)

# Fit Model
eval_set = [(X_train, y_train)]
classifier_1.fit(
    X_train,
    y_train,
    eval_set=eval_set,
    verbose=False
)

# Get predictions for training data
train_yhat = classifier_1.predict(X_train)
print("Training Preds: \n", train_yhat[:5])

# Set K-Fold Cross Validation Levels
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=1)

# Training Results
train_results = cross_val_score(classifier_1, X_train, y_train, scoring=scoring, cv=cv, n_jobs=1)

# Brief Review of Training Results
print("Average Accuracy K-Fold: ", round(train_results.mean(), 2))
print("Std Deviation K-Fold: ", round(train_results.std(), 2))
print("Precision Score 0: ", round(precision_score(y_train, train_yhat, average=None)[0], 3))
print("Precision Score 1: ", round(precision_score(y_train, train_yhat, average=None)[1], 3))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Machine: Laptop &lt;br&gt;
Processor: AMD Ryzen 7 &lt;br&gt;
OS: Windows &lt;br&gt;
DataFrame Shape: (7282, 17)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkxfai73dfdz9tjcqz69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkxfai73dfdz9tjcqz69.png" alt="Image description" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>programming</category>
      <category>learning</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Understanding How Scope Affects Values in Your Spring REST Controller</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 02 Mar 2024 03:33:59 +0000</pubDate>
      <link>https://dev.to/czetsuya/understanding-how-scope-affects-values-in-your-spring-rest-controller-4c97</link>
      <guid>https://dev.to/czetsuya/understanding-how-scope-affects-values-in-your-spring-rest-controller-4c97</guid>
      <description>&lt;p&gt;Below we explore how a scope annotation affects an instance value in a Spring REST controller.&lt;/p&gt;

&lt;p&gt;Each controller is annotated with scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@RestController
@Scope([SCOPE_VALUE])
public class XXXScopeController {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwos5ouwyufdrudx13r9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwos5ouwyufdrudx13r9.png" alt="Image description" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Convert Vertically Stored Asset Data into Columnar Format for Cointegration Analysis</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 02 Mar 2024 01:12:16 +0000</pubDate>
      <link>https://dev.to/czetsuya/how-to-convert-vertically-stored-asset-data-into-columnar-format-for-cointegration-analysis-54f9</link>
      <guid>https://dev.to/czetsuya/how-to-convert-vertically-stored-asset-data-into-columnar-format-for-cointegration-analysis-54f9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This piece of code fetches asset information from a table stored vertically. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08t3ue8ulmjcwbr4wi4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08t3ue8ulmjcwbr4wi4u.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;Install the following package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conda install pandas
conda install numpy as np
conda install mysql-connector-python
conda install sqlalchemy
conda install pymysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hands-on Coding
&lt;/h2&gt;

&lt;p&gt;Connect to the database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def query_df(query):
    try: 
        engine_uri = f"mysql+pymysql://db_user:db_pass_123@localhost:3306/tradewise_pse"
        db_conn = create_engine(engine_uri)        
        df_result = pd.read_sql(query, db_conn)    
        return df_result

    except Exception as e:    
        print(str(e))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fetching the Dataset
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not load_existing:
    sql_distinct_tickers = "select ticker from candlestick where event_time='2023-12-29' and ticker not like '^%%'"
    df_tickers = query_df(sql_distinct_tickers)

    df = pd.DataFrame(index=['event_time'])

    ### Get the candlesticks
    for ticker in df_tickers['ticker']:
        sql_ticker_col = "select event_time, close from candlestick where ticker='{0}'"
        df_temp = query_df(sql_ticker_col.format(ticker))
        df_temp.set_index('event_time', inplace=True)
        df_temp.rename(columns={'close': ticker}, inplace=True)        
        df = df.add(df_temp, fill_value=0)

    df.to_csv(file_name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Load the dataset from file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv(file_name, index_col=0)
df.drop(index=df.index[-1],axis=0, inplace=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Drop NA
&lt;/h2&gt;

&lt;p&gt;df.dropna(axis=1, inplace=True)&lt;/p&gt;

&lt;h2&gt;
  
  
  Print the Dataset
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(f"Shape: {df.shape}")
print(f"Null values: {df.isnull().values.any()}")
df

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ypd0yfpe8ihrfvx58cl.png)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Learn to Incorporate Rolling Hurst Values into Your DataFrame</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 02 Mar 2024 01:08:30 +0000</pubDate>
      <link>https://dev.to/czetsuya/learn-to-incorporate-rolling-hurst-values-into-your-dataframe-40jk</link>
      <guid>https://dev.to/czetsuya/learn-to-incorporate-rolling-hurst-values-into-your-dataframe-40jk</guid>
      <description>&lt;h2&gt;
  
  
  The hurst function.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def hurst(ts, min_lag=1, max_lag=7):
    lags = range(min_lag, max_lag)
    tau = [np.sqrt(np.std(np.subtract(ts[lag:], ts[:-lag]))) for lag in lags]
    poly = np.polyfit(np.log(lags), np.log(tau), 1)
    return poly[0]*2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding to our DataFrame
&lt;/h2&gt;

&lt;p&gt;The hurst value is computed with the last 14 close values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df['Hurst'] = df['close'].rolling(14).apply(hurst, raw=True)
df[10:20]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5huizfpyrqhicnwiekmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5huizfpyrqhicnwiekmy.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Implementing Glowroot: A Hands-On Tech Review for Application Monitoring Mastery</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Mon, 05 Feb 2024 02:16:41 +0000</pubDate>
      <link>https://dev.to/czetsuya/implementing-glowroot-a-hands-on-tech-review-for-application-monitoring-mastery-31c0</link>
      <guid>https://dev.to/czetsuya/implementing-glowroot-a-hands-on-tech-review-for-application-monitoring-mastery-31c0</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In the realms of information technology and systems management, Application Performance Management (APM) involves monitoring and overseeing the performance and availability of software applications. APM aims to identify and diagnose intricate application performance issues to uphold a predefined level of service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Glowroot&lt;/strong&gt; is an open-source APM that facilitates a quicker resolution of application performance issues by helping us pinpoint the root causes. It supports applications running from Java 6 onwards.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key Features
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Response Time Breakdown Charts: Visualizes the breakdown of response times for better analysis.&lt;/li&gt;
&lt;li&gt;Response Time Percentile Charts: Offers percentile charts to understand response time distribution.&lt;/li&gt;
&lt;li&gt;SQL Capture and Aggregation: Captures and aggregates SQL queries for in-depth analysis.&lt;/li&gt;
&lt;li&gt;Service Call Capture and Aggregation: Gathers and aggregates data on service calls for comprehensive insights.&lt;/li&gt;
&lt;li&gt;MBean Attribute Capture and Charts: Monitors and charts MBean attributes for performance evaluation.&lt;/li&gt;
&lt;li&gt;Configurable Alerting: Allows users to configure alerts based on specific criteria.&lt;/li&gt;
&lt;li&gt;Historical Rollup: Provides historical data rollup at different intervals (1m, 5m, 30m, 4h) with configurable retention settings.&lt;/li&gt;
&lt;li&gt;Full Support for Async Requests: Supports asynchronous requests that span multiple threads.&lt;/li&gt;
&lt;li&gt;Responsive UI with Mobile Support: User-friendly and responsive interface with mobile support for accessibility.&lt;/li&gt;
&lt;li&gt;Optional Central Collector: Offers the flexibility of an optional central collector for centralized data management.&lt;/li&gt;
&lt;li&gt;Supports Multiple Application Servers: Wildfly, JBoss EAP, Tomcat, TomEE, Jetty, Glassfish, Payara, WebLogic, WebSphere&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Central Collector
&lt;/h1&gt;

&lt;p&gt;The central collector collects runtime information from the registered services and offers a GUI for easy viewing. &lt;a href="https://github.com/glowroot/glowroot/wiki/Central-Collector-Installation" rel="noopener noreferrer"&gt;https://github.com/glowroot/glowroot/wiki/Central-Collector-Installation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;You can follow the steps above for running the GlowRoot central collector either as a standalone or as a docker image. For this section, I'll share how it can be run locally.&lt;br&gt;
Here's my docker-compose file for running glowroot-central with cassandra. Override username, password, and contactPoints in glowroot-central.properties.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;version: '3.8'&lt;/p&gt;

&lt;p&gt;networks:&lt;br&gt;
  tradewise-network:&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  cassandra:&lt;br&gt;
    image: cassandra:latest&lt;br&gt;
    container_name: cassandra&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    ports:&lt;br&gt;
      - "9042:9042"&lt;br&gt;
    networks:&lt;br&gt;
      - tradewise-network&lt;/p&gt;

&lt;p&gt;glowroot-central:&lt;br&gt;
    image: glowroot/glowroot-central:0.14.1&lt;br&gt;
    container_name: glowroot-central&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    volumes:&lt;br&gt;
      - ./glowroot-central.properties:/usr/share/glowroot-central/glowroot-central.properties&lt;br&gt;
    depends_on:&lt;br&gt;
      - cassandra&lt;br&gt;
    ports:&lt;br&gt;
      - "4000:4000"&lt;br&gt;
      - "8181:8181"&lt;br&gt;
    networks:&lt;br&gt;
      - tradewise-network&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Instrumentation&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;There are 4 ways in which we can integrate GlowRoot into our services  &lt;a href="https://glowroot.org/instrumentation.html" rel="noopener noreferrer"&gt;https://glowroot.org/instrumentation.html&lt;/a&gt;. For this exercise, we will focus on using the agent API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing the pom.xml File
&lt;/h2&gt;

&lt;p&gt;In our Spring Boot project's pom.xml file, add the GlowRoot agent configuration.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;&amp;lt;plugin&amp;gt;&lt;br&gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br&gt;
    &amp;lt;artifactId&amp;gt;maven-resources-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt;
    &amp;lt;executions&amp;gt;&lt;br&gt;
        &amp;lt;execution&amp;gt;&lt;br&gt;
            &amp;lt;id&amp;gt;glowroot-plugins&amp;lt;/id&amp;gt;&lt;br&gt;
            &amp;lt;phase&amp;gt;validate&amp;lt;/phase&amp;gt;&lt;br&gt;
            &amp;lt;goals&amp;gt;&lt;br&gt;
                &amp;lt;goal&amp;gt;copy-resources&amp;lt;/goal&amp;gt;&lt;br&gt;
            &amp;lt;/goals&amp;gt;&lt;br&gt;
            &amp;lt;configuration&amp;gt;&lt;br&gt;
                &amp;lt;outputDirectory&amp;gt;target/glowroot-plugins/&amp;lt;/outputDirectory&amp;gt;&lt;br&gt;
                &amp;lt;resources&amp;gt;&lt;br&gt;
                    &amp;lt;resource&amp;gt;&lt;br&gt;
                        &amp;lt;directory&amp;gt;glowroot-plugins&amp;lt;/directory&amp;gt;&lt;br&gt;
                        &amp;lt;filtering&amp;gt;false&amp;lt;/filtering&amp;gt;&lt;br&gt;
                    &amp;lt;/resource&amp;gt;&lt;br&gt;
                &amp;lt;/resources&amp;gt;&lt;br&gt;
            &amp;lt;/configuration&amp;gt;&lt;br&gt;
        &amp;lt;/execution&amp;gt;&lt;br&gt;
    &amp;lt;/executions&amp;gt;&lt;br&gt;
&amp;lt;/plugin&amp;gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;plugin&amp;gt;&lt;br&gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br&gt;
    &amp;lt;artifactId&amp;gt;maven-dependency-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt;
    &amp;lt;version&amp;gt;${maven-dependency-plugin.version}&amp;lt;/version&amp;gt;&lt;br&gt;
    &amp;lt;executions&amp;gt;&lt;br&gt;
        &amp;lt;execution&amp;gt;&lt;br&gt;
            &amp;lt;id&amp;gt;copy-glowroot-jar&amp;lt;/id&amp;gt;&lt;br&gt;
            &amp;lt;phase&amp;gt;prepare-package&amp;lt;/phase&amp;gt;&lt;br&gt;
            &amp;lt;goals&amp;gt;&lt;br&gt;
                &amp;lt;goal&amp;gt;copy&amp;lt;/goal&amp;gt;&lt;br&gt;
            &amp;lt;/goals&amp;gt;&lt;br&gt;
        &amp;lt;/execution&amp;gt;&lt;br&gt;
    &amp;lt;/executions&amp;gt;&lt;br&gt;
    &amp;lt;configuration&amp;gt;&lt;br&gt;
        &amp;lt;artifactItems&amp;gt;&lt;br&gt;
            &amp;lt;artifactItem&amp;gt;&lt;br&gt;
                &amp;lt;groupId&amp;gt;org.glowroot&amp;lt;/groupId&amp;gt;&lt;br&gt;
                &amp;lt;artifactId&amp;gt;glowroot-agent&amp;lt;/artifactId&amp;gt;&lt;br&gt;
                &amp;lt;version&amp;gt;${glowroot-agent.version}&amp;lt;/version&amp;gt;&lt;br&gt;
                &amp;lt;type&amp;gt;jar&amp;lt;/type&amp;gt;&lt;br&gt;
                &amp;lt;overWrite&amp;gt;false&amp;lt;/overWrite&amp;gt;&lt;br&gt;
                &amp;lt;outputDirectory&amp;gt;${project.build.directory}&amp;lt;/outputDirectory&amp;gt;&lt;br&gt;
                &amp;lt;destFileName&amp;gt;glowroot.jar&amp;lt;/destFileName&amp;gt;&lt;br&gt;
            &amp;lt;/artifactItem&amp;gt;&lt;br&gt;
        &amp;lt;/artifactItems&amp;gt;&lt;br&gt;
    &amp;lt;/configuration&amp;gt;&lt;br&gt;
&amp;lt;/plugin&amp;gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Preparing the Dockerfile&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;We need to add the GlowRoot files in the docker image.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;h1&gt;
  
  
  Base Image
&lt;/h1&gt;

&lt;p&gt;FROM eclipse-temurin:17-jdk-alpine&lt;br&gt;
LABEL author=CzetsuyaTech&lt;br&gt;
LABEL maintainer=CzetsuyaTech&lt;/p&gt;
&lt;h1&gt;
  
  
  Configuration
&lt;/h1&gt;

&lt;p&gt;WORKDIR /&lt;/p&gt;

&lt;p&gt;RUN addgroup --system czetsuyatech &amp;amp;&amp;amp; \&lt;br&gt;
    adduser --system czetsuyatech --ingroup czetsuyatech &amp;amp;&amp;amp; \&lt;br&gt;
    mkdir -p /glowroot /glowroot/tmp /glowroot/logs /glowroot/plugins &amp;amp;&amp;amp; \&lt;br&gt;
    echo '{ "web": { "bindAddress": "0.0.0.0" } }' &amp;gt; /glowroot/admin.json &amp;amp;&amp;amp; \&lt;br&gt;
    chown czetsuyatech:czetsuyatech -R /glowroot &amp;amp;&amp;amp; \&lt;br&gt;
    chmod -R 777 /glowroot&lt;/p&gt;

&lt;p&gt;USER czetsuyatech&lt;br&gt;
ADD --chown=czetsuyatech:czetsuyatech target/glowroot.jar /glowroot&lt;br&gt;
ADD --chown=czetsuyatech:czetsuyatech target/glowroot-plugins /glowroot/plugins&lt;/p&gt;
&lt;h1&gt;
  
  
  Service
&lt;/h1&gt;

&lt;p&gt;ADD --chown=czetsuyatech:czetsuyatech target/*.jar app.jar&lt;/p&gt;
&lt;h1&gt;
  
  
  Start
&lt;/h1&gt;

&lt;p&gt;ENV JAVA_JAR "/app.jar"&lt;br&gt;
ENV JAVA_OTHERS "-Xshare:off"&lt;br&gt;
ENV JAVA_OPTS ${JAVA_OPTS}&lt;br&gt;
ENV JAVA_MEM ${JAVA_MEM}&lt;/p&gt;

&lt;p&gt;RUN echo "exec java $JAVA_MEM $JAVA_OPTS $JAVA_OTHERS -jar $JAVA_JAR"&lt;br&gt;
ENTRYPOINT exec java $JAVA_MEM $JAVA_OPTS $JAVA_OTHERS -jar $JAVA_JAR&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Running the Spring Boot Service&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;To enable instrumentation in our instance, we need to specify the javaagent property. And to send information to the central collector we need to specify the central collector and give our instance an agent id.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JAVA_OPTS=-javaagent:glowroot/glowroot.jar -Dglowroot.collector.address=localhost:8181 -server -Dglowroot.agent.id=TradewiseAI&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  GUI
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Usage
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7aki3psigfk8syzkk3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7aki3psigfk8syzkk3d.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Transactions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web
&lt;/h3&gt;

&lt;p&gt;To assess our REST Endpoints' performance, we access the "Web" section. In the provided example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;By choosing "Response Time," we can identify which HttpRequests and JDBC Queries are taking longer in the REST Endpoints.&lt;/li&gt;
&lt;li&gt;Opting for "Slow Traces" allows us to pinpoint the specific endpoint that consumes more time.&lt;/li&gt;
&lt;li&gt;Selecting "Queries" reveals insights into the queries made, indicating that using the count query is more resource-intensive compared to the select query. This information aids in optimizing and refining the performance of REST Endpoints.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;To analyze our background performance, we navigate to the "Background" section. In the given example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choosing "Response Time" allows us to identify which Job and Hibernate Queries are consuming more time in the background processes.&lt;/li&gt;
&lt;li&gt;Opting for "Slow Traces" reveals that some calls take more time, providing insights into areas that may require attention.&lt;/li&gt;
&lt;li&gt;Selecting "Queries" and filtering by the select query, we observe that it is called more frequently and takes longer, particularly when filtered by status. This information helps in understanding and addressing potential bottlenecks in the background processes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tu9jlngltybia6nfbbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tu9jlngltybia6nfbbz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Startup
&lt;/h3&gt;

&lt;p&gt;To assess our startup performance, we can utilize the "Startup" section. In the provided example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;By choosing "Response Time," we can identify which startup and filter init processes consume more time.&lt;/li&gt;
&lt;li&gt;Opting for "Slow Traces" reveals the duration it takes for the context to initialize fully.&lt;/li&gt;
&lt;li&gt;Selecting "Queries" provides insights into the database queries. Some queries will be more resource intensive, with fewer calls but longer duration. This information aids in pinpointing specific areas for optimization within the startup processes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web
&lt;/h3&gt;

&lt;p&gt;To visualize errors in our REST endpoints, we can navigate to the "Web" section. In the following example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing "Error Messages" reveals errors of type XXXException.&lt;/li&gt;
&lt;li&gt;Opting for "Error Traces" provides details on the errors.&lt;/li&gt;
&lt;li&gt;Clicking on a specific error allows us to view the detailed trace, aiding in the understanding and resolution of the issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  JVM
&lt;/h2&gt;

&lt;p&gt;In our services' JVM, we can monitor and analyze memory status. This allows us to gain insights into the memory usage patterns, allocations, and overall health of the Java Virtual Machine, aiding in the effective management and optimization of our services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdo3q22y5422r8y15at5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdo3q22y5422r8y15at5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MBeanTree
&lt;/h2&gt;

&lt;p&gt;The MBeanTree functionality in Glowroot proves invaluable in monitoring instance creation. For instance, to track thread-related metrics and identify potential Thread Leaks, users can navigate to the java.lang section, specifically under Threading. This allows for a detailed examination of thread-related information, aiding in the identification and resolution of potential thread-related issues, such as Thread Leaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo68n8lnr4ex1wid1n5gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo68n8lnr4ex1wid1n5gb.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Recommendation
&lt;/h1&gt;

&lt;p&gt;In a typical product infrastructure, Glowroot serves as our APM tool in each microservice. All microservices are instrumented to gather and transmit data to Glowroot, enhancing our understanding of system behavior. We've chosen Glowroot based on various criteria, including its license-free nature, alignment with the Java ecosystem, and straightforward instrumentation for microservices, ensuring a simple ramp-up and configuration process.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>productivity</category>
      <category>java</category>
      <category>learning</category>
    </item>
    <item>
      <title>Navigating the Code: A Guide to Environment Variables, Configuration, and Feature Flags</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Thu, 14 Dec 2023 00:10:28 +0000</pubDate>
      <link>https://dev.to/czetsuya/navigating-the-code-a-guide-to-environment-variables-configuration-and-feature-flags-3c07</link>
      <guid>https://dev.to/czetsuya/navigating-the-code-a-guide-to-environment-variables-configuration-and-feature-flags-3c07</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93g7xodr20h2fvcvl07h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93g7xodr20h2fvcvl07h.jpg" alt="App Configuration" width="800" height="532"&gt;&lt;/a&gt;&lt;br&gt;
Determining the optimal location for storing essential information crucial for an application's functionality necessitates thoughtful planning. This guide aims to assist you in making informed decisions about where to store crucial data. Here, the term "application" encompasses any software created with code and deployed on various platforms and runtimes, irrespective of the underlying software architecture.&lt;/p&gt;

&lt;p&gt;A fundamental principle is to avoid hard-coding any values within your application. Instead, adopt the practice of retrieving all configuration settings from a distinct storage system, separate from the deployment environment. This approach allows for seamless modifications to configuration settings even after the application has been deployed, eliminating the need for redeployment. This separation ensures greater flexibility and facilitates the adjustment of critical parameters without disrupting the operational state of your deployed application.&lt;/p&gt;

&lt;p&gt;When considering the storage location for key/value pairs, it's crucial to evaluate the volatility of each pair—how frequently and by whom it might change. Reflect on the dynamic nature of the data and the responsibilities associated with its modification. This thoughtful approach will guide you in choosing an appropriate storage solution that aligns with the specific needs and characteristics of each key/value pair. By understanding the frequency and ownership of potential changes, you can make informed decisions that optimize the efficiency and maintainability of your data storage strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;Environment variables, characterized by their infrequent changes, typically occur only once per deployment. Common examples include sensitive information like usernames and passwords for databases, which are often stored securely in a Vault. Additionally, environment variables encompass essential details required for the system's initialization, such as environment specifics and the connection details for the application configuration store, which hosts additional settings. By utilizing environment variables for these foundational aspects, you ensure a secure and stable system setup while centralizing critical configuration information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Configuration
&lt;/h2&gt;

&lt;p&gt;Application configuration, in contrast to environment variables, operates independently of deployments. It encompasses supplementary settings for the system that have the potential to change between deployments at runtime. This flexibility allows for real-time adjustments to the application's behavior and features, accommodating evolving requirements without the need for redeployment. By leveraging application configuration for these dynamic settings, you establish a modular and adaptable structure, enhancing the responsiveness of your system to changing runtime conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime User Settings
&lt;/h2&gt;

&lt;p&gt;Runtime user settings represent the most volatile category, as users can alter any key/value pair at any given moment. Therefore, the system must be designed to accommodate this high level of dynamism. Options for handling such volatility include implementing robust real-time validation mechanisms and ensuring the integrity and security of user-initiated changes. Additionally, the system should provide an intuitive user interface for managing these settings, enabling seamless customization while maintaining overall system stability and security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here are three options for handling configuration updates:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Re-read Configuration All the Time:&lt;/strong&gt; Continuously monitor and re-read the configuration, ensuring that the system remains up-to-date with any changes. This approach provides real-time responsiveness to modifications but may impose a continuous processing overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-read Configuration at Set Intervals (e.g., Every 15 Minutes):&lt;/strong&gt; Implement a periodic schedule to re-read the configuration at predefined intervals, such as every 15 minutes. This approach balances responsiveness with reduced processing overhead, making it suitable for scenarios where near-real-time updates are acceptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receive Push Notifications When New Application Configuration Is Available:&lt;/strong&gt; Set up a push notification mechanism to alert the system when new application configuration becomes available. This approach minimizes the need for continuous or scheduled re-reading, optimizing efficiency by updating the system only when changes occur. However, it requires a reliable notification infrastructure. Choosing among these options depends on the specific requirements of your system, considering factors such as the criticality of real-time updates, resource constraints, and the overall responsiveness desired for configuration changes.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>appconfig</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Java XML Validation: How to Validate an XML Document against an XSD</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 01 Jul 2023 11:58:22 +0000</pubDate>
      <link>https://dev.to/czetsuya/java-xml-validation-how-to-validate-an-xml-document-against-an-xsd-nn7</link>
      <guid>https://dev.to/czetsuya/java-xml-validation-how-to-validate-an-xml-document-against-an-xsd-nn7</guid>
      <description>&lt;p&gt;Introducing our Java XML Validation Project! Master the art of validating XML effortlessly against XSD in a Java environment. Gain hands-on experience, ensuring the integrity and conformity of your data.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Introduction
&lt;/h1&gt;

&lt;p&gt;Introducing our Java XML Validation Project! Gain hands-on experience in validating XML documents against XSD with ease. This project serves as a practical guide, equipping you with the necessary knowledge and tools to ensure the integrity and conformity of your XML data in a Java environment. Discover the simplicity of our step-by-step approach, which walks you through the entire validation process. Master the art of validating XML effortlessly and unlock a new level of confidence in your data. Join us on this journey as we empower you to harness the power of XML validation in your Java projects. &lt;/p&gt;

&lt;h1&gt;
  
  
  2. Generating Java Classes from XSD
&lt;/h1&gt;

&lt;p&gt;For this exercise, we will be using an XSD from w3schools  &lt;a href="https://www.w3schools.com/xml/schema_example.asp"&gt;https://www.w3schools.com/xml/schema_example.asp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To be able to generate the Java classes from XSD, we will be using Eclipse Enterprise Edition. IntelliJ can also do it with the Jakarta plugin, but Eclipse performs better, especially when dealing with XJB files.&lt;/p&gt;

&lt;p&gt;IntelliJ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclhm3821xl2sfg2b33lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclhm3821xl2sfg2b33lj.png" alt="Image description" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eclipse JAXB Classes Generator&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft98dbv71fkwtl60jak2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft98dbv71fkwtl60jak2r.png" alt="Image description" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generated Classes&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceuz1p5bxrwxqkepueka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceuz1p5bxrwxqkepueka.png" alt="Image description" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Dependencies
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.fasterxml.jackson.dataformat&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jackson-dataformat-xml&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;jakarta.activation&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jakarta.activation-api&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;jakarta.xml.bind&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jakarta.xml.bind-api&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${jakarta.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.glassfish.jaxb&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jaxb-runtime&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${jakarta.version}&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  4. Core Classes
&lt;/h1&gt;

&lt;p&gt;To run the validations and throw the appropriate exception or name of the tag where a constraint is violated, we will need the following classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.1 ResourceResolver
&lt;/h2&gt;

&lt;p&gt;To import an external XSD, for example, if we will be using the XML digital signature.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd"/&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class ResourceResolver implements LSResourceResolver {

  private static final Logger LOGGER = LoggerFactory.getLogger(ResourceResolver.class);

  public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {

    // note: in this sample, the XSD's are expected to be in the root of the classpath
    InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("xsd/" + systemId);
    return new Input(publicId, systemId, resourceAsStream);
  }

  static class Input implements LSInput {

    private String publicId;

    private String systemId;

    public String getStringData() {

      String textDataFromFile;

      try {
        BufferedReader bufferReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder stringBuilder = new StringBuilder();
        String eachStringLine;

        while ((eachStringLine = bufferReader.readLine()) != null) {
          stringBuilder.append(eachStringLine).append("\n");
        }
        textDataFromFile = stringBuilder.toString();

        return textDataFromFile;

      } catch (IOException e) {
        LOGGER.error("Fail reading from inputStream={}", e.getMessage());
        return null;
      }
    }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4.2 SaxErrorHandler
&lt;/h2&gt;

&lt;p&gt;For us to be able to know the tag where a constraint exception is thrown, we need to define a custom exception that accepts an XMLStreamReader object where we can get the localName property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class SaxErrorHandler implements ErrorHandler {

  private XMLStreamReader reader;

  public SaxErrorHandler(XMLStreamReader reader) {
    this.reader = reader;
  }

  @Override
  public void error(SAXParseException e) {
    warning(e);
  }

  @Override
  public void fatalError(SAXParseException e) {
    warning(e);
  }

  @Override
  public void warning(SAXParseException e) {

    throw new XmlValidationException(reader.getLocalName());
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4.3 Miscellaneous Classes
&lt;/h2&gt;

&lt;p&gt;We will also be needing utility classes for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a JAXBElement&lt;/li&gt;
&lt;li&gt;Convert JAXBElement to XMLStreamReader&lt;/li&gt;
&lt;li&gt;Defining the correct marshaller object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These classes are all available in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.4 XmlSchemaValidator Interface
&lt;/h2&gt;

&lt;p&gt;And finally, the interface that pieces together all components to do the validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default void validateXMLSchema(String xsdPath, JAXBElement&amp;lt;?&amp;gt; xml) {

  Validator validator = null;
  try {
    SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
    factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
    factory.setResourceResolver(new ResourceResolver());

    Schema schema = factory.newSchema(ResourceUtils.getFile(xsdPath));
    validator = schema.newValidator();
    XMLStreamReader xmlStreamReader = asStreamReader(xml);

    ErrorHandler errorHandler = new SaxErrorHandler(xmlStreamReader);
    validator.setErrorHandler(errorHandler);

    validator.validate(new StAXSource(xmlStreamReader));

  } catch (IOException | SAXException | JAXBException | XMLStreamException e) {

    var xmlEx = getXmlValidationExceptionIfPresent(e);
    throw new InvalidXmlException(xmlEx.getLocalName(), e);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  5. Source Code / Git Repository
&lt;/h1&gt;

&lt;p&gt;The complete source code for this project is available for my sponsors at &lt;a href="https://github.com/czetsuyatech/java-xml-validation-by-xsd"&gt;https://github.com/czetsuyatech/java-xml-validation-by-xsd&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sponsorship link: &lt;a href="https://github.com/sponsors/czetsuya"&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Project Screenshot&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxybdgpwcqcu0pnhv9mkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxybdgpwcqcu0pnhv9mkf.png" alt="Image description" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>xml</category>
      <category>xsd</category>
    </item>
    <item>
      <title>Unlocking Google Calendar API with Keycloak: A Developer's Guide</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 20 May 2023 07:52:14 +0000</pubDate>
      <link>https://dev.to/czetsuya/unlocking-google-calendar-api-with-keycloak-a-developers-guide-nkn</link>
      <guid>https://dev.to/czetsuya/unlocking-google-calendar-api-with-keycloak-a-developers-guide-nkn</guid>
      <description>&lt;h1&gt;
  
  
  1. Overview
&lt;/h1&gt;

&lt;p&gt;Unlock the power of Keycloak for seamless authentication and authorization in your applications. This comprehensive guide is designed for software developers who want to integrate Keycloak into their systems to enable secure user login and token exchange with Google. &lt;/p&gt;

&lt;p&gt;By following step-by-step instructions and best practices, you'll learn how to leverage Keycloak's robust features to facilitate seamless authentication and obtain a Google token. With this token, you can effortlessly access the Google Calendar API and unlock a wealth of functionality for your application. Enhance user experience and streamline calendar integration - start implementing Keycloak and Google integration today.&lt;/p&gt;

&lt;p&gt;You need prior knowledge with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  2. Google
&lt;/h1&gt;

&lt;p&gt;Before we can do the integration with Keycloak we need to create a project in Google Console, create a credential and obtain OAuth client id and secret.&lt;/p&gt;

&lt;p&gt;If you haven't done so, you can create a Google console account at  &lt;a href="https://cloud.google.com/cloud-console"&gt;https://cloud.google.com/cloud-console&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.1 Creating the Google Cloud Project
&lt;/h2&gt;

&lt;p&gt;At the top left of your Google Cloud Console, click the project and click New Project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f7pwciuh7wp4n5t38jt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f7pwciuh7wp4n5t38jt.png" alt="Image description" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I call this project "Lab".&lt;/p&gt;

&lt;h2&gt;
  
  
  2.2 Create OAuth2 Client
&lt;/h2&gt;

&lt;p&gt;The next step is to create the OAuth2 client credential that will give us the client id and secret. This combination will be used when we set up the Google Identity provider in Keycloak.&lt;/p&gt;

&lt;p&gt;With the Lab project selected, find API &amp;amp; Services / Credentials in the menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00auccu017eizp2gmmyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00auccu017eizp2gmmyr.png" alt="Image description" width="800" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create Credentials with the following inputs:&lt;/p&gt;

&lt;p&gt;Type: OAuth client ID&lt;br&gt;
Application type: Web application&lt;br&gt;
Name: Lab&lt;/p&gt;

&lt;p&gt;There are two important entries in this section:&lt;/p&gt;

&lt;p&gt;Authorized Javascript origins&lt;/p&gt;

&lt;p&gt;This should be the URL where the request for login will come from.&lt;/p&gt;

&lt;p&gt;Authorized redirect URIs&lt;/p&gt;

&lt;p&gt;This entry is a common cause of problems if not properly set up. This is where Google will redirect the user after a successful login.&lt;/p&gt;

&lt;p&gt;Some common redirect URIs.&lt;/p&gt;

&lt;p&gt;Amazon Cognito: &lt;a href="https://lab.auth.ap-southeast-1.amazoncognito.com/oauth2/idpresponse"&gt;https://lab.auth.ap-southeast-1.amazoncognito.com/oauth2/idpresponse&lt;/a&gt;&lt;br&gt;
Postman: &lt;a href="https://www.getpostman.com/oauth2/callback"&gt;https://www.getpostman.com/oauth2/callback&lt;/a&gt;&lt;br&gt;
Keycloak broker: &lt;a href="http://localhost:8080/realms/czetsuyatech/broker/google/endpoint"&gt;http://localhost:8080/realms/czetsuyatech/broker/google/endpoint&lt;/a&gt;&lt;br&gt;
Keycloak token: &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have a redirect issue, this is one of the first things you may want to check.&lt;/p&gt;

&lt;p&gt;This is how my Lab client looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdbhmhk1u8gw5631b383.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdbhmhk1u8gw5631b383.png" alt="Image description" width="800" height="1179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the same page, you should be able to see the client id and secret. Take note because we will need them later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx392nxc34knlq2h98hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx392nxc34knlq2h98hd.png" alt="Image description" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2.3 Setup OAuth Consent Screen
&lt;/h2&gt;

&lt;p&gt;This section will ask for several application-related settings. You can enter values as you see fit, but normally I set the following.&lt;/p&gt;

&lt;p&gt;Non-sensitive scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auth-userinfo-email&lt;/li&gt;
&lt;li&gt;auth-userinfo-profile&lt;/li&gt;
&lt;li&gt;openid
Don't forget to add a test user. We will use it to login later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61xi2r6zkimt7tx2hnwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61xi2r6zkimt7tx2hnwp.png" alt="Image description" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  3. Keycloak
&lt;/h1&gt;

&lt;p&gt;For this exercise, we need to create a new Keycloak realm and client. We must enable authentication in the client.&lt;/p&gt;
&lt;h2&gt;
  
  
  3.1 Client
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hqne0pzftaxnv2ioxsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hqne0pzftaxnv2ioxsr.png" alt="Image description" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enabling authentication will give us access to Keycloak's client and secret, which we will need later for testing. Take note of it.&lt;/p&gt;

&lt;p&gt;We also need to download the Adapter config of our newly created client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlo3u7i4tgunx9zur6cs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlo3u7i4tgunx9zur6cs.png" alt="Image description" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downloaded config should look like this. Save it as we will use it in our Spring Boot application later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "realm": "czetsuyatech",
  "auth-server-url": "http://localhost:8080/",
  "ssl-required": "external",
  "resource": "web-front",
  "credentials": {
    "secret": "BxYWD11qCAW6ZybPTy6b9M8ej0thTxxx"
  },
  "confidential-port": 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3.2 Identity Provider
&lt;/h2&gt;

&lt;p&gt;Identity providers are third-party services that allow users to log in to Keycloak. In our example, we will do an authorization as well by exchanging the Keycloak token to Google to allow us to call Google API services such as Calendar and Gmail.&lt;/p&gt;

&lt;p&gt;So login to Keycloak as admin, click Identity Providers on the left side, Click Add Provider, and select Google.&lt;/p&gt;

&lt;p&gt;In the Client ID and secret fields, enter the values that we saved when we were configuring the Google client earlier.&lt;/p&gt;

&lt;p&gt;Under Advance settings, change the value of Scopes to "&lt;em&gt;email profile openid &lt;a href="https://www.googleapis.com/auth/calendar"&gt;https://www.googleapis.com/auth/calendar&lt;/a&gt; &lt;a href="https://www.googleapis.com/auth/calendar.events"&gt;https://www.googleapis.com/auth/calendar.events&lt;/a&gt; &lt;a href="https://www.googleapis.com/auth/gmail.readonly"&gt;https://www.googleapis.com/auth/gmail.readonly&lt;/a&gt;&lt;/em&gt;", without double quotes. This will give us permission to call list endpoints from Calendar and Gmail APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr79unbkhq7322ou8tx11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr79unbkhq7322ou8tx11.png" alt="Image description" width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3.3 Permissions
&lt;/h2&gt;

&lt;p&gt;In this section, we will give our client permission to exchange tokens with the newly created Google identity provider.&lt;/p&gt;

&lt;p&gt;On the identity provider page, click the Permissions tab. Toggle Permissions enabled to ON. Under the Permission list, click token-exchange.&lt;/p&gt;

&lt;p&gt;Set the Decision strategy to Unanimous. Click Save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypp6qqvktgxmvcoraj7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypp6qqvktgxmvcoraj7d.png" alt="Image description" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to create the permission scope. At the top-left of the page, click Client details. It should redirect you to the Authorization tab. Open the Policies sub-tab. Create a new policy with the following values:&lt;/p&gt;

&lt;p&gt;Type: Client&lt;br&gt;
Name: google-token-exchange&lt;br&gt;
Clients: web-front (the Keycloak client we created earlier)&lt;br&gt;
Logic: Positive&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ocn5te2c92kzfszr1ut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ocn5te2c92kzfszr1ut.png" alt="Image description" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to your Google identity provider’s permission page and set the Policies to the one we just created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf69pcrjffxo8prgiwfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf69pcrjffxo8prgiwfa.png" alt="Image description" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup should now be ready to exchange tokens.&lt;/p&gt;
&lt;h1&gt;
  
  
  4. Testing
&lt;/h1&gt;

&lt;p&gt;To test the exchange of tokens between Keycloak and Google, I have created a Postman collection where we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate a user&lt;/li&gt;
&lt;li&gt;Exchange Keycloak to Google token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we proceed with the testing, we need to run a Keycloak instance. I have prepared a docker-compose to do that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxti4bl4amtfthj44pwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxti4bl4amtfthj44pwc.png" alt="Image description" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to authenticate our user, so create a new POST request in Postman and use OAuth2 authorization. Set the URL to &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7w6egwndvph1aiingsh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7w6egwndvph1aiingsh.png" alt="Image description" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Be sure to set the client id and secret to the ones we saved earlier when we created the Keycloak client.&lt;/p&gt;

&lt;p&gt;For the truncated values:&lt;/p&gt;

&lt;p&gt;Callback URL: &lt;a href="https://www.getpostman.com/oauth2/callback"&gt;https://www.getpostman.com/oauth2/callback&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Auth URL: &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/auth"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/auth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access Token URL: &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Get New Access Token. It should redirect us to a Google login page, use the test email address that you added earlier when creating the Google client.&lt;/p&gt;

&lt;p&gt;The next page will show you the scopes that will be permitted for your user. We defined this when we created the Google identity provider earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6x4q39v0evkx1xniduf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6x4q39v0evkx1xniduf.png" alt="Image description" width="800" height="889"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Allow.&lt;/p&gt;

&lt;p&gt;Copy the access token and paste it on a bearerToken variable. The succeeding requests will have Authorization Type = Bearer and will use this value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce5fkzuuyjxdw9jlks8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce5fkzuuyjxdw9jlks8q.png" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Body of the request must have the following URL encoded parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11pv1gu93k9p5opk1yvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11pv1gu93k9p5opk1yvj.png" alt="Image description" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Send. It should return with the following response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lsentnlwlf38yea2df6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lsentnlwlf38yea2df6.png" alt="Image description" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the token that we can use to access Google APIs.&lt;/p&gt;

&lt;p&gt;Obviously, we need to automate the process if we wanted this feature in our application. Thus, I have created a Spring project to do it.&lt;/p&gt;
&lt;h1&gt;
  
  
  5. Spring Project
&lt;/h1&gt;

&lt;p&gt;This project demonstrates the token-exchange feature available on Keycloak. With this, we can exchange Keycloak for Google’s token, which we can use to call Google APIs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This project is only available for my sponsors &lt;a href="https://github.com/sponsors/czetsuya"&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5.1 Available Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Get the bearer token from the request and store it in a Bean that can be autowired.&lt;/li&gt;
&lt;li&gt;Exchange Keycloak token to Google, this will allow us to call Google APIs (Calendar, Gmail).&lt;/li&gt;
&lt;li&gt;Use Spring exchange to provide abstraction in calling Google APIs.&lt;/li&gt;
&lt;li&gt;Endpoints to list Calendar events and Gmail messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The heart of the application exchanges tokens between Keycloak and Google.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Component
public class GoogleTokenExchange implements TokenExchange {

  @Override
  public String exchangeToken(String accessToken) {

    AuthzClient authzClient = AuthzClient.create();
    Configuration keycloakConfig = authzClient.getConfiguration();
    String baseUrl = authzClient.getServerConfiguration().getTokenEndpoint();

    RestTemplate restTemplate = new RestTemplate();

    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED.toString());
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    MultiValueMap&amp;lt;String, String&amp;gt; requestBody = new LinkedMultiValueMap&amp;lt;&amp;gt;();
    requestBody.add("client_id", keycloakConfig.getResource());
    requestBody.add("client_secret", keycloakConfig.getCredentials().get("secret").toString());
    requestBody.add("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
    requestBody.add("subject_token_type", "urn:ietf:params:oauth:token-type:access_token");
    requestBody.add("subject_token", accessToken);
    requestBody.add("requested_issuer", "google");

    HttpEntity formEntity = new HttpEntity&amp;lt;&amp;gt;(requestBody, headers);

    ResponseEntity&amp;lt;AccessTokenResponse&amp;gt; response = restTemplate.exchange(baseUrl, HttpMethod.POST, formEntity,
        AccessTokenResponse.class);

    return response.getBody().getToken();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5.2 Testing
&lt;/h2&gt;

&lt;p&gt;If you have subscribed as my sponsor, you will be given access to my repository and proceed with this testing.&lt;/p&gt;

&lt;p&gt;The project provides two endpoints, to test calling list calendar events and Gmail messages from Google. Matching Postman requests are also available to make testing easier.&lt;/p&gt;

&lt;p&gt;As we have done earlier, we need to get an access token from Keycloak and set it as bearerToken environment variable inside Postman.&lt;/p&gt;

&lt;p&gt;Get Calendar Events Response&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihegcw8glo30slgohqr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihegcw8glo30slgohqr1.png" alt="Image description" width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get Gmail Messages&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzk1skgdifekuyt62ybri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzk1skgdifekuyt62ybri.png" alt="Image description" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Source Code
&lt;/h1&gt;

&lt;p&gt;All the resources used in this article and demo are available on the GitHub repository &lt;a href="https://github.com/czetsuyatech/auth-exchange"&gt;https://github.com/czetsuyatech/auth-exchange&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring project source code&lt;/li&gt;
&lt;li&gt;Docker/compose file&lt;/li&gt;
&lt;li&gt;Keycloak realm&lt;/li&gt;
&lt;li&gt;Postman collection for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  7. References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange"&gt;https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.keycloak.org/docs/latest/server_development/index.html#retrieving-external-idp-tokens"&gt;https://www.keycloak.org/docs/latest/server_development/index.html#retrieving-external-idp-tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/calendar"&gt;https://developers.google.com/calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/gmail/api/guides"&gt;https://developers.google.com/gmail/api/guides&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>keycloak</category>
      <category>googleapi</category>
      <category>tokenexchange</category>
      <category>spring</category>
    </item>
    <item>
      <title>Implementing X509 Certificate Validation in Java: A Step-by-Step Guide</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sun, 07 May 2023 05:09:15 +0000</pubDate>
      <link>https://dev.to/czetsuya/implementing-x509-certificate-validation-in-java-a-step-by-step-guide-1963</link>
      <guid>https://dev.to/czetsuya/implementing-x509-certificate-validation-in-java-a-step-by-step-guide-1963</guid>
      <description>&lt;p&gt;Unlock the power of secure communication with Java! Learn how to implement X509 certificate validation simply and straightforwardly, step-by-step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;An X.509 certificate is a digital certificate used to verify a particular entity's identity, such as a website or an individual. X.509 certificates are widely used in various applications, including secure communication protocols like HTTPS, SSL, and TLS.&lt;/p&gt;

&lt;p&gt;An X.509 certificate contains several pieces of information, including the identity of the entity being verified, the public key that is associated with that entity, and various other pieces of metadata that provide additional information about the certificate itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importance
&lt;/h2&gt;

&lt;p&gt;One of the key benefits of X.509 certificates is that they allow entities to authenticate themselves to other parties securely and reliably. This can help to prevent various types of attacks, including man-in-the-middle attacks, where an attacker intercepts and modifies communication between two parties to steal sensitive information.&lt;/p&gt;

&lt;p&gt;Overall, X.509 certificates play a critical role in modern security infrastructure and are essential to many different types of secure communication protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;You must have the following installed on your computer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Maven&lt;/li&gt;
&lt;li&gt;IDE (IntellIJ / Visual Studio)&lt;/li&gt;
&lt;li&gt;Java 17+&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;This project uses the following dependencies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bcpkix-jdk15on - certificate revocation check&lt;/li&gt;
&lt;li&gt;spring-boot-starter-test (for testing)&lt;/li&gt;
&lt;li&gt;lombok (for logging)&lt;/li&gt;
&lt;li&gt;Certificates from &lt;a href="https://www.digicert.com/kb/digicert-root-certificates.htm"&gt;https://www.digicert.com/kb/digicert-root-certificates.htm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Code
&lt;/h2&gt;

&lt;p&gt;The certificate validation process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo5re8jpbffczc99kfcm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo5re8jpbffczc99kfcm.png" alt="Image description" width="441" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To implement the validation, we will introduce a bean where we can define the:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Certificate that we are validating&lt;/li&gt;
&lt;li&gt;The pem file that we can cross-check #1&lt;/li&gt;
&lt;li&gt;Root certificate to check if the certificate is already revoked&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's examine the code.&lt;br&gt;
The CertificateBundle class is just a simple Java bean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Getter
@Setter
@Builder
public class CertificateBundle {

  // Certificate extracted from the message
  private X509Certificate certificate;

  // Certificate loaded from the application must match the certificate
  private X509Certificate confirmationCertificate;

  // RootCertificate of the certificate
  private X509Certificate rootCertificate;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To validate the &lt;strong&gt;certificate expiration&lt;/strong&gt;, we need to extract the X509Certificate object and perform a time comparison against the value of getNotAfter(). Here, we're making sure that the current date is greater than the certificate's expiry date.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date expiresOn = cert.getNotAfter();
Date now = new Date();

if (now.getTime() &amp;gt; expiresOn.getTime()) {
  throw new ExpiredCertificateException();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Comparing digital signature against a PEM file&lt;/strong&gt;. In most fintech applications, there is a need to sign the message before sending it to another service. Upon receiving the message, the server needs to extract the public certificate embedded in it and compare it to a PEM file. This PEM file is a public key of the private key certificate used to sign the message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuzd6ogloenj1giz01uz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuzd6ogloenj1giz01uz.png" alt="Image description" width="437" height="301"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;boolean match = Arrays.equals(cert1.getPublicKey().getEncoded(),
    cert2.getPublicKey().getEncoded());

if (!match) {
  throw new MismatchCertificateException("Embedded certificate does not match the given PEM");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, we do the &lt;strong&gt;revocation check&lt;/strong&gt;. This process allows us to know whether a website's certificate is trustworthy or not. It uses the root certificate that we used for signing as the trust anchor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  List&amp;lt;X509Certificate&amp;gt; certs = Collections.singletonList(cb.getCertificate());
  CertificateFactory cf = CertificateFactory.getInstance("X509");
  CertPath cp = cf.generateCertPath(certs);

  // load the root CA cb
  X509Certificate rootCACert = cb.getRootCertificate();

  // init trusted certs
  TrustAnchor ta = new TrustAnchor(rootCACert, null);
  Set&amp;lt;TrustAnchor&amp;gt; trustedCerts = new HashSet&amp;lt;&amp;gt;();
  trustedCerts.add(ta);

  // init PKIX parameters
  PKIXParameters params = new PKIXParameters(trustedCerts);

  // load the CRL
  params.addCertStore(CertStore.getInstance("Collection",
      new CollectionCertStoreParameters(getX509Crls(cf, getCrl(cb.getCertificate())))));

  // perform validation
  CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
  PKIXCertPathValidatorResult cpvResult = (PKIXCertPathValidatorResult) cpv.validate(cp,
      params);
  X509Certificate trustedCert = cpvResult.getTrustAnchor().getTrustedCert();

  if (trustedCert == null) {
    log.error("Trusted certificate not found");
    throw new CertificateException("Trusted certificate not found");

  } else {
    log.debug("Trusted CA DN = " + trustedCert.getSubjectX500Principal());
  }

} catch (InvalidAlgorithmParameterException | java.security.cert.CertificateException |
         CRLException |
         NoSuchAlgorithmException |
         CertPathValidatorException |
         IOException e) {
  throw new RevokedCertificateException(e);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>java</category>
      <category>digitalsigning</category>
      <category>digitalcertificate</category>
    </item>
  </channel>
</rss>
