<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6581802047354010081</id><updated>2026-03-18T19:34:04.485+08:00</updated><category term="java"/><category term="c#"/><category term="spring"/><category term="frontend"/><category term="javaee"/><category term="database"/><category term="web"/><category term="xcode"/><category term="java-getting-started"/><category term="source-code-management"/><category term="rdbms"/><category term="os"/><category term="spring-rest"/><category term="Programming"/><category term="application-server"/><category term="wildfly"/><category term="maven"/><category term="php"/><category term="spring-microservice"/><category term="web-platform"/><category term="ubuntu"/><category term="keycloak"/><category term="work-from-home"/><category term="java-eclipse-rcp"/><category term="git"/><category term="javaee-rest"/><category term="react"/><category term="java-troubleshooting"/><category term="wordpress"/><category term="java-library"/><category term="angular"/><category term="coding"/><category term="java-persistence"/><category term="Spring Series"/><category term="authentication"/><category term="aws"/><category term="docker"/><category term="DB/Server"/><category term="jsf"/><category term="quarkus"/><category term="rbdms"/><category term="serverless"/><category term="spring-cloud"/><category term="spring-data"/><category term="troubleshooting"/><category term="windows"/><category term="device"/><category term="devops"/><category term="persistence"/><category term="technology-education"/><category term="Infrastructure"/><category term="algorithms"/><category term="authentication-server"/><category term="computer-science"/><category term="container"/><category term="Database Series"/><category term="cloud-computing"/><category term="cognito"/><category term="java-jsf"/><category term="java-testing"/><category term="jenkins"/><category term="nextjs"/><category term="software-engineering"/><category term="spring-security"/><category term="database-version-control"/><category term="integration"/><category term="java-android"/><category term="java-eclipse"/><category term="machine learning"/><category term="programming-exercise"/><category term="spring-testing"/><category term="svn"/><category term="youtube-playlist"/><category term="Application Server Series"/><category term="Authentication Server Series"/><category term="Cloud Computing Series"/><category term="Container Series"/><category term="Source Code Management Series"/><category term="java-collections"/><category term="java-encryption"/><category term="java-hello-world"/><category term="jpa"/><category term="networking"/><category term="spring-getting-started"/><category term="trading"/><category term="Computer Science Series"/><category term="Courses"/><category term="OS Series"/><category term="Spring Courses"/><category term="android"/><category term="big-data"/><category term="cloud"/><category term="database-tools"/><category term="dataframe"/><category term="elk"/><category term="event-streaming"/><category term="getting-started"/><category term="glowroot"/><category term="grafana"/><category term="jasper"/><category term="jasperreports"/><category term="java-concurrency"/><category term="java-io"/><category term="java-java-rest"/><category term="java-javaee"/><category term="jobs"/><category term="kafka"/><category term="logging"/><category term="mac"/><category term="magento"/><category term="prometheus"/><category term="review"/><category term="spring-config"/><category term="startup"/><category term="work-at-home"/><title type='text'>czetsuyatech</title><subtitle type='html'> This blog contains tutorials, how-to&#39;s, and tips for programmers, engineers, and anyone else who would like to learn programming and tools/plugins used by professional programmers.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default?start-index=26&amp;max-results=25'/><author><name>avcarreon</name><uri>http://www.blogger.com/profile/07645787568344634224</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>769</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-584812358318607788</id><published>2025-11-04T09:24:00.003+08:00</published><updated>2025-11-04T09:26:52.847+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><title type='text'>How to Log Method Execution Time in Spring Boot Using AOP</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;Overview&lt;/h2&gt;&lt;div&gt;&lt;p data-end=&quot;1119&quot; data-start=&quot;475&quot;&gt;Monitoring method execution time is one of the easiest yet most effective ways to understand your application’s performance. In this guide, we’ll explore how to use &lt;strong data-end=&quot;655&quot; data-start=&quot;640&quot;&gt;&lt;a data-preview=&quot;&quot; href=&quot;https://www.google.com/search?ved=1t:260882&amp;amp;q=Spring+Boot&amp;amp;bbid=6581802047354010081&amp;amp;bpid=584812358318607788&quot; target=&quot;_blank&quot;&gt;Spring Boot&lt;/a&gt;&lt;/strong&gt; with &lt;strong data-end=&quot;698&quot; data-start=&quot;661&quot;&gt;Aspect-Oriented Programming (AOP)&lt;/strong&gt; to log method execution times cleanly—without cluttering your business logic. By leveraging the &lt;code data-end=&quot;804&quot; data-start=&quot;795&quot;&gt;@Aspect&lt;/code&gt; annotation, we can intercept method calls, measure how long they take, and log this information for performance analysis. This approach is flexible, reusable, and perfect for tracking &lt;a data-preview=&quot;&quot; href=&quot;https://www.google.com/search?ved=1t:260882&amp;amp;q=performance+bottlenecks+analysis+tools&amp;amp;bbid=6581802047354010081&amp;amp;bpid=584812358318607788&quot; target=&quot;_blank&quot;&gt;performance bottlenecks&lt;/a&gt; in large-scale applications. Let’s dive in and build a lightweight &lt;a data-preview=&quot;&quot; href=&quot;https://www.google.com/search?ved=1t:260882&amp;amp;q=Spring+AOP+execution+time+logger+example&amp;amp;bbid=6581802047354010081&amp;amp;bpid=584812358318607788&quot; target=&quot;_blank&quot;&gt;execution time logger&lt;/a&gt; using Spring AOP.&lt;/p&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Aspect Annotation
&lt;/h2&gt;&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackExecutionTime {
 //
}
&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;
Aspect Interceptor
&lt;/h2&gt;&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@Aspect
@Component
@Slf4j
public class ExecutionTimeAspect {

  @Around(&quot;@annotation(com.dcx.nba.actions.annotations.TrackExecutionTime)&quot;)
  public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

    long startTime = System.currentTimeMillis();

    Object result = joinPoint.proceed();

    long endTime = System.currentTimeMillis();
    long executionTime = endTime - startTime;

    log.info(&quot;{} executed in {}ms&quot;, joinPoint.getSignature().toShortString(), executionTime);

    return result;
  }
}
&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;
Annotation Usage
&lt;/h2&gt;&lt;div&gt;This method runs via scheduler and computes the total elapsed time during the execution&lt;/div&gt;&lt;pre class=&quot;brush: java&quot;&gt;@Override
@TrackExecutionTime
@SchedulerLock(name = JOB_NAME)
@Scheduled(cron = &quot;${app.schedulers.update-action-status.cron}&quot;)
public void scheduleUpdateStatus() {
}
&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/584812358318607788/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/584812358318607788?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/584812358318607788'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/584812358318607788'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2025/11/how-to-log-method-execution-time-in-spring-boot-using-aop.html' title='How to Log Method Execution Time in Spring Boot Using AOP'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-5860202626945923728</id><published>2025-07-13T19:31:00.007+08:00</published><updated>2025-07-20T20:41:04.632+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="authentication"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-security"/><title type='text'>Secure React and Spring Boot with Microsoft Entra SSO and Bearer Token Authentication</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;Introduction&lt;/h2&gt;&lt;div&gt;Implementing secure, seamless login across a modern full-stack application is essential—but it doesn’t have to be complicated. In this guide, we’ll walk through how to integrate Microsoft Entra (formerly Azure AD) for Single Sign-On (SSO) in a React frontend and Spring Boot backend. The Spring Boot application acts as a resource server, validating JWT bearer tokens issued by Entra. Beyond authentication, we’ll also demonstrate how to map Microsoft Entra groups to internal application roles, dynamically fetched from your own database. Whether you&#39;re building internal tools or enterprise-grade platforms, this setup gives your app robust security, fine-grained access control, and a smooth user experience across services.&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Problem&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;You need SSO in your frontend to allow users to login to your system and authorize the call of backend endpoints by sending the bearer token. In the backend we will get the user group attached to the currently log user and fetch the corresponding internal permissions which we will add as roles to the JWT.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Our schema will look like:&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAaCBZcW8WxfhqBiSSs_rt3zFVxoVbYrVmbPJOsT0Y0_uedLB0Tkz1HUuQpb22bewVTc9EMUXt95dE5frjTp96f2oTsMx4PvleObWIKYb0HB19uHfh59yvGazCkaflfrnGsTD63FGeqbdVLtaBukIjVJBcMGAaUk1lw4UKCprjRn-13pvXLLacBA7uNoYm/s481/Role%20Permission.drawio.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;41&quot; data-original-width=&quot;481&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAaCBZcW8WxfhqBiSSs_rt3zFVxoVbYrVmbPJOsT0Y0_uedLB0Tkz1HUuQpb22bewVTc9EMUXt95dE5frjTp96f2oTsMx4PvleObWIKYb0HB19uHfh59yvGazCkaflfrnGsTD63FGeqbdVLtaBukIjVJBcMGAaUk1lw4UKCprjRn-13pvXLLacBA7uNoYm/s16000/Role%20Permission.drawio.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Prerequisites for this Exercise&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Microsoft Entrata account (&lt;a href=&quot;https://entra.microsoft.com&quot;&gt;https://entra.microsoft.com&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Familiar with React&lt;/li&gt;&lt;li&gt;Experience in Spring REST programming&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Microsoft Entrata&lt;/h2&gt;&lt;div&gt;If you haven&#39;t sign up yet visit Microsoft Entrata website and register a new account. After a successful registration you should be logged and redirect to the dashboard page where you can get the tenant id. Take a note because we will use it in the client and backend configuration later.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbHE11U_0GmaPOdgzQCgprUI0i9tf3PcwHZ_vl4XQbLeQW9dArMmzUTB586cH69LZefk_FiRdD2YklZhGPgETEOF-nao703SJ8QD7psIQ20XGBicmpC2LKVIaMP64us-V4ywz241WDPojB_GwZ0zRNRp5kt-CXaHvUPwoTcrCmHPPsESCP3KjFn0c1F3of/s1465/1%20-%20MS%20Entrata%20dashboard.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;469&quot; data-original-width=&quot;1465&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbHE11U_0GmaPOdgzQCgprUI0i9tf3PcwHZ_vl4XQbLeQW9dArMmzUTB586cH69LZefk_FiRdD2YklZhGPgETEOF-nao703SJ8QD7psIQ20XGBicmpC2LKVIaMP64us-V4ywz241WDPojB_GwZ0zRNRp5kt-CXaHvUPwoTcrCmHPPsESCP3KjFn0c1F3of/s16000/1%20-%20MS%20Entrata%20dashboard.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;App Registration&lt;/h3&gt;&lt;div&gt;Inside Entrata, under Manage / App Registrations click New Registrations. For this exercise since we will be doing the authentication in the frontend we need to register an app of type &lt;b&gt;SPA&lt;/b&gt;. Don&#39;t forget to fill-in the redirect URI.&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS15Z202tF2PW5HezIG9a-wZ5-Y54Bfc-mVVx1szxiTxwcV9OgDQFnyhn4SE4LGgpAnNnxWToM-Wd-IL6w3GJvHqu1kUA-6ogIgRrsf_Px7R7YffUi24glW3KtPRILGYYF-WHaKvsCvpefj2A00blHtGeqZNcKIpQSPhkyhiIRlMlbAOihMQZcqL2H247g/s1547/2%20-%20App%20registration.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1149&quot; data-original-width=&quot;1547&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS15Z202tF2PW5HezIG9a-wZ5-Y54Bfc-mVVx1szxiTxwcV9OgDQFnyhn4SE4LGgpAnNnxWToM-Wd-IL6w3GJvHqu1kUA-6ogIgRrsf_Px7R7YffUi24glW3KtPRILGYYF-WHaKvsCvpefj2A00blHtGeqZNcKIpQSPhkyhiIRlMlbAOihMQZcqL2H247g/s16000/2%20-%20App%20registration.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Take note of the Application (client) ID.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcvT2YWnDsg7-myH6WQTjh2GqJmbeG5UsBOZeoJHQSPKw_KSQvkNUZF4gXnNQ3i0z5RIRb25w4jhjTObkLiiNgzKW_hmsMIbAv7TaBStmQHOYcYj1-foDjJIiUU6Q558GXi5f52TUDeJIVWjddxrzrXQYztiG8cayJxrBUZjlRv82FImwIU1PI62YdPN5Q/s782/3%20-%20App%20client%20detail.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;328&quot; data-original-width=&quot;782&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcvT2YWnDsg7-myH6WQTjh2GqJmbeG5UsBOZeoJHQSPKw_KSQvkNUZF4gXnNQ3i0z5RIRb25w4jhjTObkLiiNgzKW_hmsMIbAv7TaBStmQHOYcYj1-foDjJIiUU6Q558GXi5f52TUDeJIVWjddxrzrXQYztiG8cayJxrBUZjlRv82FImwIU1PI62YdPN5Q/s16000/3%20-%20App%20client%20detail.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Expose an API&lt;/h3&gt;&lt;div&gt;Now we need to add a scope that we will use during login. This will allow us to use the MS Graph version 2. Without a defined scope Entrata will use version 1 which will throw an error during JWT validation in the backend.&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggZMf-WD1YuvOy8CQhzF30Nu4rDGF2bOZbG7wt1z12r6-V5w0nkTUt-hCp_hBoWXTR810z_2vNIevImBO_9suFdpm_HsMbfGWzXymmWj4pRyPIyJNBM2S1JoQOeH6ZV4lBpsuTdDfYlp8XmIGgzOzvnHO3r-OQvYeVAbLkXSMONNo9uhBUuU1ntCD9JGYM/s1059/4%20-%20Add%20a%20scope.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1059&quot; data-original-width=&quot;782&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggZMf-WD1YuvOy8CQhzF30Nu4rDGF2bOZbG7wt1z12r6-V5w0nkTUt-hCp_hBoWXTR810z_2vNIevImBO_9suFdpm_HsMbfGWzXymmWj4pRyPIyJNBM2S1JoQOeH6ZV4lBpsuTdDfYlp8XmIGgzOzvnHO3r-OQvYeVAbLkXSMONNo9uhBUuU1ntCD9JGYM/s16000/4%20-%20Add%20a%20scope.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Update Token Version in the Manifest&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;In the left menu, find the Manifest.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;In the JSON document, find the&amp;nbsp;&lt;b&gt;requestedAccessTokenVersion &lt;/b&gt;and set its value to 2.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Note that the change takes time to propagate.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgim93Q2I8oDXM-M7b5r7cnphSYXhhjgI8j21LSdL50JmxfrNKk6G-8XNQe4sF_fRWnIEksb5QY04veiCpbyAEoR1C8_jeZ-8RLoWpFGFA6aomrelfzc2gWltLSNekyYQ55CTK8Bq_ikljNyTHmGN07sYCr_D0_XXTaQs9okO8YnqrYzLau8Z0ujO2wxj8u/s1351/5%20-%20Manifest.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;701&quot; data-original-width=&quot;1351&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgim93Q2I8oDXM-M7b5r7cnphSYXhhjgI8j21LSdL50JmxfrNKk6G-8XNQe4sF_fRWnIEksb5QY04veiCpbyAEoR1C8_jeZ-8RLoWpFGFA6aomrelfzc2gWltLSNekyYQ55CTK8Bq_ikljNyTHmGN07sYCr_D0_XXTaQs9okO8YnqrYzLau8Z0ujO2wxj8u/s16000/5%20-%20Manifest.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;div&gt;&lt;h2&gt;&lt;br /&gt;&lt;/h2&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Now, our Entrata app is ready for integration.&lt;/p&gt;&lt;div&gt;&lt;h2&gt;React SPA Client Application&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;This exercise will use a SPA project provided by Microsoft available at&amp;nbsp;&lt;a href=&quot;https://github.com/Azure-Samples/ms-identity-ciam-javascript-tutorial/tree/main/2-Authorization/1-call-api-react/SPA&quot;&gt;https://github.com/Azure-Samples/ms-identity-ciam-javascript-tutorial/tree/main/2-Authorization/1-call-api-react/SPA&lt;/a&gt;. It uses MSAL library to authenticate the users and access secured APIs by acquiring security tokens from Microsoft Entrata.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;SPA Modifications&lt;/h3&gt;&lt;div&gt;We need to do several updates on the project.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Open &lt;i&gt;authConfig.js&lt;/i&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Replace the &lt;b&gt;clientId&lt;/b&gt; with the value of Application (client) Id that we have generated when we register the app in Entrata.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Update the value of &lt;b&gt;authority &lt;/b&gt;using the Directory / Tenant Id.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiv-FCb7z6YaE0hDAusB3k5-NUZivQBvnMS3fSWEhF4Lwt_C0AFnmewkM5q8IjqhftxFZtzuMV-em_-uSt0iGxF-__VhdpQnnyvMj3l2D0OMv0LgpZLr9W1XZ2JzE39vdxxNPlqSkxymSC8apxAT-s5g7kcZRvaHltkb3j2PXuc6hzvsGTLhZoKnxJ9ZKvq/s1378/6%20-%20SPA%20config.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;291&quot; data-original-width=&quot;1378&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiv-FCb7z6YaE0hDAusB3k5-NUZivQBvnMS3fSWEhF4Lwt_C0AFnmewkM5q8IjqhftxFZtzuMV-em_-uSt0iGxF-__VhdpQnnyvMj3l2D0OMv0LgpZLr9W1XZ2JzE39vdxxNPlqSkxymSC8apxAT-s5g7kcZRvaHltkb3j2PXuc6hzvsGTLhZoKnxJ9ZKvq/s16000/6%20-%20SPA%20config.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Then to enable Graph V2 when signing in, we need to request access to the scope which we defined earlier.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;In the same file &lt;i&gt;authConfig.js, &lt;/i&gt;at the end of the file.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_Y0qO1H-DAdYWmG2kSG-kQoGY6SZEsFrCAi3oRYYEmE-0IrhvryFTUr0OOYJcqG7-pkU2TnfxL2opp3AIEg9BLhdYrP05ezev8swqT-5_YZ7h3nRWawYrIWfCilwUegLOKyVy7tRurnFuH2ZF0VKKYJ8pu24feIcHc6Vqz3zphEvfjpMilUQ66EQ_Gj8M/s1217/7%20-%20Add%20scope%20me.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;706&quot; data-original-width=&quot;1217&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_Y0qO1H-DAdYWmG2kSG-kQoGY6SZEsFrCAi3oRYYEmE-0IrhvryFTUr0OOYJcqG7-pkU2TnfxL2opp3AIEg9BLhdYrP05ezev8swqT-5_YZ7h3nRWawYrIWfCilwUegLOKyVy7tRurnFuH2ZF0VKKYJ8pu24feIcHc6Vqz3zphEvfjpMilUQ66EQ_Gj8M/s16000/7%20-%20Add%20scope%20me.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;The SPA is now ready to login via SSO using Microsoft Entrata.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Backend Spring Service&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;We will start with an empty Spring Boot project and add each piece.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Maven Dependencies&lt;/h3&gt;&lt;div&gt;We need to have at least the following. This project will exchange Entrata group mapping it to an internal role map to permissions as shown in the diagram above. The permissions will be used as roles.&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5kuQ3jHcAgBiAyiKZcMotB-_NdZ9ZCHEukpE3HYFC_U_NEvSnqUb9dvJlycTufoJSzaje4-padK8ufTxAvMq6JVCCua1wQbdVmX2ErLuGxsmzzJIzgQuTIsxZYpTRiRwWSNf6BWQAsdzVtJaOD2JgoDyRXO7cEgZ14wRF9dGcrB8YuXxC1SzEFBR3dLsX/s1169/8%20-%20Dependencies.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1091&quot; data-original-width=&quot;1169&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5kuQ3jHcAgBiAyiKZcMotB-_NdZ9ZCHEukpE3HYFC_U_NEvSnqUb9dvJlycTufoJSzaje4-padK8ufTxAvMq6JVCCua1wQbdVmX2ErLuGxsmzzJIzgQuTIsxZYpTRiRwWSNf6BWQAsdzVtJaOD2JgoDyRXO7cEgZ14wRF9dGcrB8YuXxC1SzEFBR3dLsX/s16000/8%20-%20Dependencies.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Convert Entrata Group to Internal Roles&lt;/h3&gt;&lt;div&gt;In this step, we will be using our internal tables where roles and permissions are stored. The Entrata group&#39;s UUID is searched in the auth_role table and the role&#39;s permissions are added as authorities to the JWT token.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW4po4hupDHsCxytC1gX13ADgT1r0yI6vdnE9WsMdzTQDHeypns97AJvXBvEP9tqA9iQV05Rf8HPSKda3j2gMgu4-Yv33CENyNXunyEDfpxKDRGER4ZckdHRYUSbToE1ZqfOmxYr6AsOohlw82DANHZKsOVeYWUoMff3gDI7qnmoyvs5ajWP-W1jsDc1et/s1375/9%20-%20Entrata%20group%20conversion.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;971&quot; data-original-width=&quot;1375&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW4po4hupDHsCxytC1gX13ADgT1r0yI6vdnE9WsMdzTQDHeypns97AJvXBvEP9tqA9iQV05Rf8HPSKda3j2gMgu4-Yv33CENyNXunyEDfpxKDRGER4ZckdHRYUSbToE1ZqfOmxYr6AsOohlw82DANHZKsOVeYWUoMff3gDI7qnmoyvs5ajWP-W1jsDc1et/s16000/9%20-%20Entrata%20group%20conversion.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;This converter is used as the AuthenticationConverter when setting up the oauth2ResourceServer in the securityFilterChain. Furthermore, we need to set the resourceserver&#39;s issuer-uri in the application yml file. This URL will be used when validating the token.&lt;h2 style=&quot;text-align: left;&quot;&gt;Testing&amp;nbsp;&lt;/h2&gt;&lt;div&gt;To test the integration we will be creating a new controller with PreAuthorize annotated endpoints validating the authority bound to the JWT token.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOyaxgXxHk5-PR6trELo692MwGw-3hCFLP7OUpk09WYQw53J4GuWFvfnepNnUN4KEpOtPq3It1D41WvBVVPS3hiN1u9cTkmXyPKh4jDZ6pb8lHfCYrY_m_gmhgDMWeiaZfEoMgruWlkHQivkwF3nVR_4m7yeDO2fiTzK-Uzhb4Q063sjunjRV-SlzkmBTP/s1337/10%20-%20Testing.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1337&quot; data-original-width=&quot;1241&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOyaxgXxHk5-PR6trELo692MwGw-3hCFLP7OUpk09WYQw53J4GuWFvfnepNnUN4KEpOtPq3It1D41WvBVVPS3hiN1u9cTkmXyPKh4jDZ6pb8lHfCYrY_m_gmhgDMWeiaZfEoMgruWlkHQivkwF3nVR_4m7yeDO2fiTzK-Uzhb4Q063sjunjRV-SlzkmBTP/s16000/10%20-%20Testing.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To enable the method level security we need to annotate a configuration class with&amp;nbsp;&lt;b&gt;@EnableMethodSecurity&lt;/b&gt;.&lt;/div&gt;&lt;div&gt;&lt;h2&gt;Development and Support&lt;/h2&gt;&lt;div&gt;The Spring project used in this tutorial is available at&amp;nbsp;&lt;a href=&quot;https://github.com/czetsuyatech/spring-ms-entrata-oauth&quot;&gt;https://github.com/czetsuyatech/spring-ms-entrata-oauth&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unlock the full coding experience! As a&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot; target=&quot;_blank&quot;&gt;GitHub Sponsor&lt;/a&gt;, you gain exclusive access to the code behind this article—start learning and building today!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;m available for contracting services and support. You can reach me at:&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/5860202626945923728/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/5860202626945923728?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5860202626945923728'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5860202626945923728'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2025/07/spring-sso-with-microsoft-entrata.html' title='Secure React and Spring Boot with Microsoft Entra SSO and Bearer Token Authentication'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAaCBZcW8WxfhqBiSSs_rt3zFVxoVbYrVmbPJOsT0Y0_uedLB0Tkz1HUuQpb22bewVTc9EMUXt95dE5frjTp96f2oTsMx4PvleObWIKYb0HB19uHfh59yvGazCkaflfrnGsTD63FGeqbdVLtaBukIjVJBcMGAaUk1lw4UKCprjRn-13pvXLLacBA7uNoYm/s72-c/Role%20Permission.drawio.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-1302458893965737422</id><published>2025-03-04T15:27:00.003+08:00</published><updated>2025-03-30T09:30:47.494+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring-data"/><title type='text'>Spring Data JPA: Creating Dynamic Search Filters with Specifications</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Introduction&lt;/h2&gt;
&lt;div&gt;In this article, I will share an implementation that utilizes a common base class to streamline the process of implementing search functionality in Spring Data JPA using Specifications. By creating a reusable base class, you can efficiently manage and extend your search logic across multiple entities. This approach reduces redundancy and promotes cleaner, more maintainable code while allowing for dynamic query creation based on varying search criteria. Whether you&#39;re dealing with simple or complex filtering requirements, this solution will simplify your data querying layer in Spring applications.&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Supported Operations&lt;/h2&gt;
&lt;div&gt;The following operations are supported:&lt;/div&gt;
&lt;div&gt;
  &lt;ul style=&quot;text-align: left;&quot;&gt;
    &lt;li&gt;greater than&lt;/li&gt;
    &lt;li&gt;less than&lt;/li&gt;
    &lt;li&gt;greater than or equal&lt;/li&gt;
    &lt;li&gt;less than or equal&lt;/li&gt;
    &lt;li&gt;equal&lt;/li&gt;
    &lt;li&gt;isnull&lt;/li&gt;
    &lt;li&gt;like&lt;/li&gt;
  &lt;/ul&gt;
  &lt;div&gt;These operation will be listed as enum for easier reference.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Base Model&lt;/h2&gt;
&lt;div&gt;We will use a user entity to demonstrate the filter.&lt;/div&gt;
&lt;div&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;package com.czetsuyatech.persistence.entities;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;

@Table(name = &quot;user_profile&quot;)
@Entity
@Data
public class UserEntity extends BaseEntity {

  @Column(name = &quot;first_name&quot;)
  private String firstName;

  @Column(name = &quot;last_name&quot;)
  private String lastName;

  @Column(name = &quot;role&quot;)
  private String role;

  @Column(name = &quot;age&quot;)
  private int age;
}

  &lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Class / Flow Diagram&lt;/h2&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL8TJEoY2aupZbp729iGtoreiPvEgywQjdm6w3aQLpxWg2E_pJZOteMMpuIc52rYqMPsEei1Qx52UaOv3D93wal-lvbzK4PrKvaN11RfsonOaSRbaQ3N5UEpmaZXspo3SUW8953l9RTZwXHda363xnezaLJTfHNvIxQxaqKJXSEUDowBUkxNPPNIHQGakA/s481/Spring-Data-Search-with-Specification.drawio.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;441&quot; data-original-width=&quot;481&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL8TJEoY2aupZbp729iGtoreiPvEgywQjdm6w3aQLpxWg2E_pJZOteMMpuIc52rYqMPsEei1Qx52UaOv3D93wal-lvbzK4PrKvaN11RfsonOaSRbaQ3N5UEpmaZXspo3SUW8953l9RTZwXHda363xnezaLJTfHNvIxQxaqKJXSEUDowBUkxNPPNIHQGakA/s16000/Spring-Data-Search-with-Specification.drawio.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;As we can see above we will need a query builder that accepts UserSpecification and UserFields to generate the user entity specification that we need to feed to the UserRepository extending SliceJpaRepository, which is implemented by SimpleSliceJpaRepositoryImpl.&amp;nbsp;&lt;/div&gt;&lt;h2 style=&quot;clear: both; text-align: left;&quot;&gt;5. Repositories and Services&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;5.1 Anatomy of the user repository&lt;/h3&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;@Repository
public interface UserRepository extends SliceJpaRepository&amp;lt;UserEntity, Long&amp;gt; {

}
&lt;/pre&gt;
  &lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@NoRepositoryBean
public interface SliceJpaRepository&amp;lt;ENTITY, ID extends Serializable&amp;gt; extends JpaRepository&amp;lt;ENTITY, ID&amp;gt;,
    JpaSpecificationExecutor&amp;lt;ENTITY&amp;gt;, JpaSpecificationExecutorWithProjection&amp;lt;ENTITY&amp;gt; {

  Slice&amp;lt;ENTITY&amp;gt; findAllSlice(@Nullable Specification&amp;lt;ENTITY&amp;gt; specification, Pageable pageable);
}
&lt;/pre&gt;
&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;
5.2 Query Builder&lt;/h3&gt;&lt;div&gt;The query builder accepts the available filterable parameters of the user entity and the specification which define how we are going to implement the filter using criteria builder.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;public enum UserSearchFields {

  FIRSTNAME {
    @Override
    public String toString() {
      return &quot;firstName&quot;;
    }
  },
  LASTNAME {
    @Override
    public String toString() {
      return &quot;lastName&quot;;
    }
  },
  ROLE {
    @Override
    public String toString() {
      return &quot;role&quot;;
    }
  },
  AGE {
    @Override
    public String toString() {
      return &quot;age&quot;;
    }
  }
}
&lt;/pre&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@AllArgsConstructor
public class UserSpecification implements Specification&amp;lt;UserEntity&amp;gt; {

  private transient SearchCriteria criteria;

  @Override
  public Predicate toPredicate(Root&amp;lt;UserEntity&amp;gt; root, CriteriaQuery&amp;lt;?&amp;gt; query, CriteriaBuilder criteriaBuilder) {

    UserSearchFields key = UserSearchFields.valueOf(StringUtils.upperCase(criteria.getKey()));
    RelationalOperators operator = RelationalOperators.getOperator(criteria.getOperation());
    Object value = criteria.getValue();
    Expression&amp;lt;?&amp;gt; expression = root.&amp;lt;String&amp;gt;get(key.toString());

    Predicate predicate = null;

    switch (operator) {
      case LIKE -&amp;gt; predicate = buildLike(key, value, expression, criteriaBuilder);
      case EQUAL -&amp;gt; predicate = buildEqual(key, value, expression, criteriaBuilder);
      case GREATER, GREATER_THAN_EQUAL -&amp;gt; predicate = buildGreater(key, value, expression, criteriaBuilder);
      case LESS, LESS_THAN_EQUAL -&amp;gt; predicate = buildLess(key, value, expression, criteriaBuilder);
      default -&amp;gt; throw new IllegalStateException(&quot;Unexpected value: &quot; + operator);
    }

    return predicate;
  }

  private Predicate buildLess(UserSearchFields key, Object value, Expression&amp;lt;?&amp;gt; expression,
      CriteriaBuilder criteriaBuilder) {

    return criteriaBuilder.lessThanOrEqualTo((Expression&amp;lt;Integer&amp;gt;) expression, Integer.parseInt(value.toString()));
  }

  private Predicate buildGreater(UserSearchFields key, Object value, Expression&amp;lt;?&amp;gt; expression,
      CriteriaBuilder criteriaBuilder) {

    return criteriaBuilder.greaterThanOrEqualTo((Expression&amp;lt;Integer&amp;gt;) expression, Integer.parseInt(value.toString()));
  }

  private Predicate buildEqual(UserSearchFields key, Object value, Expression&amp;lt;?&amp;gt; expression,
      CriteriaBuilder criteriaBuilder) {

    return criteriaBuilder.equal(expression, value);
  }

  private Predicate buildLike(UserSearchFields key, Object value, Expression&amp;lt;?&amp;gt; expression,
      CriteriaBuilder criteriaBuilder) {

    return criteriaBuilder.like((Expression&amp;lt;String&amp;gt;) expression,
        value.toString() + SpecificationConstant.LIKE_WILDCARD);
  }
}
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.3 The user service that invokes the repository.&lt;/h3&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

  private final UserRepository userRepository;
  private final UserMapper userMapper;

  @Transactional(readOnly = true)
  @Override
  public Slice&amp;lt;UserDTO&amp;gt; findUsers(String searchParams, Pageable pageable) {

    try {
      Specification&amp;lt;UserEntity&amp;gt; specResult = QueryBuilder.build(searchParams, UserSpecification.class,
          UserSearchFields.class);

      return userRepository.findAllSlice(specResult, pageable).map(userMapper::userToUserDTO);

    } catch (Exception e) {
      e.printStackTrace();
    }

    return new SliceImpl&amp;lt;&amp;gt;(Collections.emptyList());
  }
}
&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;
6. Controller&lt;/h2&gt;&lt;div&gt;And this is how we are going to call the service from the controller.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@RequestMapping(&quot;/users&quot;)
@RestController
@RequiredArgsConstructor
@Slf4j
public class UserController {

  private final UserService userService;

  @GetMapping
  public ResponseEntity&amp;lt;Slice&amp;lt;UserDTO&amp;gt;&amp;gt; findUsers(@RequestParam(name = &quot;search&quot;, required = false) String search,
      @PageableDefault Pageable pageable) {

    log.debug(&quot;Search: &quot; + search);

    return ResponseEntity.ok(userService.findUsers(search, pageable));
  }
}
&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;7. Postman Tests&lt;/h2&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg_w6Q6meQfUMxroTF64vCcO3K9AG0lAWZiUibrFGk2WLzfCua4LxWibiXwXTm4zDNwYXDz5mMv5UYSw-PYqhN-hhGyFgAFqdvSgHSLHevGtMiMSqyAzn8vhvZ-ZquP6SvUS_qIhr_cX8_cTs7KCTkEAi3VbxT3f5ShpP2XdYSE5yH8cdh6Dgn8V3HhzoJQ&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;898&quot; data-original-width=&quot;1072&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg_w6Q6meQfUMxroTF64vCcO3K9AG0lAWZiUibrFGk2WLzfCua4LxWibiXwXTm4zDNwYXDz5mMv5UYSw-PYqhN-hhGyFgAFqdvSgHSLHevGtMiMSqyAzn8vhvZ-ZquP6SvUS_qIhr_cX8_cTs7KCTkEAi3VbxT3f5ShpP2XdYSE5yH8cdh6Dgn8V3HhzoJQ=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;8. Development and Support&lt;/h2&gt;&lt;div&gt;Unlock the full coding experience! As a &lt;a href=&quot;https://github.com/sponsors/czetsuya&quot; target=&quot;_blank&quot;&gt;GitHub Sponsor&lt;/a&gt;, you gain exclusive access to the code behind this article—start learning and building today!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;m available for contracting services and support. You can reach me at:&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/1302458893965737422/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/1302458893965737422?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/1302458893965737422'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/1302458893965737422'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2025/03/spring-data-jpa-creating-dynamic-search-filters-with-specification.html.html' title='Spring Data JPA: Creating Dynamic Search Filters with Specifications'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL8TJEoY2aupZbp729iGtoreiPvEgywQjdm6w3aQLpxWg2E_pJZOteMMpuIc52rYqMPsEei1Qx52UaOv3D93wal-lvbzK4PrKvaN11RfsonOaSRbaQ3N5UEpmaZXspo3SUW8953l9RTZwXHda363xnezaLJTfHNvIxQxaqKJXSEUDowBUkxNPPNIHQGakA/s72-c/Spring-Data-Search-with-Specification.drawio.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-9027534657043846550</id><published>2024-12-07T08:27:00.002+08:00</published><updated>2024-12-07T08:27:28.133+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="java"/><title type='text'>How to Handle Daylight Saving Time Conversion in Java</title><content type='html'>&lt;p&gt;Learn how to handle daylight saving time (DST) conversions in Java with clear, practical code examples. This guide focuses on using the right classes and methods to accurately convert dates and times across DST boundaries. Perfect for developers looking to simplify time zone management in their applications.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Code&lt;/h2&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Version 1&lt;/h3&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public class TimeZoneConversion {
    public static void main(String[] args) {
        // Example UTC date-time
        // String utcDateTimeStr = &quot;2024-11-01T00:00:00&quot;;
		String utcDateTimeStr = &quot;2024-03-24T00:00:00&quot;;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss&quot;);
 
        // Parse UTC date-time
        LocalDateTime utcDateTime = LocalDateTime.parse(utcDateTimeStr, formatter);
 
        // Convert to UTC ZonedDateTime
        ZonedDateTime utcZonedDateTime = utcDateTime.atZone(ZoneId.of(&quot;UTC&quot;));
 
        // Convert to Netherlands time zone
        ZonedDateTime netherlandsTime = utcZonedDateTime.withZoneSameInstant(ZoneId.of(&quot;Europe/Amsterdam&quot;));
 
        // Format and print the result
        System.out.println(&quot;UTC Time: &quot; + utcZonedDateTime);
        System.out.println(&quot;Netherlands Time: &quot; + netherlandsTime.format(formatter));
    }
}
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;Version 2&lt;/h3&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
public class TimeZoneConversion {
    public static void main(String[] args) {
		TimeZone tz = TimeZone.getTimeZone(&quot;Europe/Amsterdam&quot;);
		TimeZone.setDefault(tz);
		Calendar cal = Calendar.getInstance(tz, Locale.ITALIAN);
		DateFormat df = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm&quot;, Locale.ITALIAN);
		Date dateBeforeDST = df.parse(&quot;2018-03-25 01:55&quot;);
		cal.setTime(dateBeforeDST);
		cal.add(Calendar.MINUTE, 10);

		System.out.println(cal.get(Calendar.ZONE_OFFSET));
		System.out.println(cal.get(Calendar.DST_OFFSET));
	}
}
&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/9027534657043846550/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/9027534657043846550?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/9027534657043846550'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/9027534657043846550'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/12/how-to-handle-daylight-saving-time.html' title='How to Handle Daylight Saving Time Conversion in Java'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-111934355531639029</id><published>2024-06-09T16:37:00.006+08:00</published><updated>2025-03-30T21:49:31.897+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="jasper"/><category scheme="http://www.blogger.com/atom/ns#" term="jasperreports"/><category scheme="http://www.blogger.com/atom/ns#" term="java"/><title type='text'>How to Create a Jasper Report from an XML Datasource: A Step-by-Step Guide</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Introduction&lt;/h2&gt;
&lt;div&gt;JasperReports is an open-source Java reporting tool that allows developers to create sophisticated reports for display, printing, or exporting into a variety of formats such as PDF, HTML, CSV, and others. It&#39;s widely used in Java-based applications for generating pixel-perfect reports with rich content like charts, tables, and images. JasperReports provides a flexible and customizable framework for designing and generating reports from various data sources, including databases, XML files, and custom data sources. It&#39;s often integrated into Java web applications and enterprise systems for generating dynamic and professional-looking reports.&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Use Case&lt;/h2&gt;
&lt;div&gt;In this article, we will take a look at how Opencell an open-source billing platform integrates Jasper reports to generate its invoicing documents.&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;The goal of this exercise is to:&lt;/div&gt;
&lt;div&gt;
	&lt;ol style=&quot;text-align: left;&quot;&gt;
		&lt;li&gt;Generate a financial document (invoice) using Jasper reports.&lt;/li&gt;
		&lt;li&gt;Use a custom font.&lt;/li&gt;
		&lt;li&gt;Use an XML document.&lt;/li&gt;
	&lt;/ol&gt;
	&lt;div&gt;Using XML as a data source in JasperReports can be ideal for several reasons:&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;div&gt;
		&lt;ol style=&quot;text-align: left;&quot;&gt;
			&lt;li&gt;
				&lt;b&gt;Flexibility:&lt;/b&gt; XML is a highly flexible data format that can represent structured data in a hierarchical manner. This makes it suitable for a wide range of data sources, including relational databases, web services, and custom data structures.
			
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;Platform-agnostic:&lt;/b&gt; XML is platform-independent, meaning it can be easily generated and consumed by different programming languages and technologies. This makes it a versatile choice for integrating with various systems and applications.
			
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;Ease of Integration:&lt;/b&gt; Many applications and systems already generate or consume XML data, so using XML as a data source can simplify integration with existing infrastructure.
			
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;Customizability:&lt;/b&gt; XML allows developers to define custom data structures and schemas tailored to their specific reporting needs. This enables precise control over the data consumed by JasperReports and the structure of the resulting reports.
			
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;Separation of Concerns:&lt;/b&gt; Using XML as a data source promotes a clean separation between data and presentation layers. This separation allows for easier maintenance, scalability, and reuse of report templates across different datasets.
			
			&lt;/li&gt;
			&lt;li&gt;
				&lt;b&gt;Performance:&lt;/b&gt; When properly optimized, XML processing can offer good performance characteristics for report generation. Techniques such as indexing, caching, and streaming can be employed to efficiently handle large XML datasets.
			
			&lt;/li&gt;
		&lt;/ol&gt;
	&lt;/div&gt;
	&lt;div&gt;Overall, XML serves as a versatile and effective data source for JasperReports, offering flexibility, interoperability, and ease of integration with diverse data sources and systems.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Coding&lt;/h2&gt;
&lt;div&gt;We&#39;ll develop a Java application to produce a PDF invoice by utilizing a Jasper template sourced from an XML data provider.&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;3.1 XML Datasource&lt;/h3&gt;
&lt;div&gt;Our XML document is generated from Opencell that contains customer and usage information.&lt;/div&gt;
&lt;div&gt;
	&lt;pre class=&quot;brush: java&quot;&gt;  &amp;lt;invoice customerAccountCode=&quot;CA_2000000021&quot; customerId=&quot;CUST_2000000021&quot; id=&quot;101&quot;
    invoiceCounter=&quot;00000027&quot; number=&quot;INV00000027&quot; templateName=&quot;invoice&quot; type=&quot;INV&quot;&amp;gt;
  &amp;lt;header&amp;gt;
    &amp;lt;provider code=&quot;CT&quot; description=&quot;CZETSUYATECH&quot;&amp;gt;
      &amp;lt;bankCoordinates&amp;gt;
        &amp;lt;ics&amp;gt;FRXXZZZ123456&amp;lt;/ics&amp;gt;
        &amp;lt;iban&amp;gt;FR763000600001123456790189&amp;lt;/iban&amp;gt;
        &amp;lt;bic&amp;gt;CTDEFRPPCCT&amp;lt;/bic&amp;gt;
      &amp;lt;/bankCoordinates&amp;gt;
    &amp;lt;/provider&amp;gt;
    &amp;lt;customer brand=&quot;&quot; category=&quot;CLIENT&quot; code=&quot;CUST_2000000021&quot; externalRef1=&quot;&quot; externalRef2=&quot;&quot;
        id=&quot;565&quot; jobTitle=&quot;&quot; registrationNo=&quot;&quot; sellerCode=&quot;CZETSUYATECH&quot; vatNo=&quot;&quot;&amp;gt;
      &amp;lt;address&amp;gt;
        &amp;lt;address1/&amp;gt;
        &amp;lt;address2/&amp;gt;
        &amp;lt;address3/&amp;gt;
        &amp;lt;city/&amp;gt;
        &amp;lt;postalCode/&amp;gt;
        &amp;lt;state/&amp;gt;
        &amp;lt;country/&amp;gt;
        &amp;lt;countryName/&amp;gt;
      &amp;lt;/address&amp;gt;
      &amp;lt;contact/&amp;gt;
    &amp;lt;/customer&amp;gt;
    &amp;lt;seller code=&quot;CZETSUYATECH&quot; description=&quot;CZETSUYATECH&quot;&amp;gt;
      &amp;lt;address&amp;gt;
        &amp;lt;address1/&amp;gt;
        &amp;lt;address2/&amp;gt;
        &amp;lt;address3/&amp;gt;
        &amp;lt;city/&amp;gt;
        &amp;lt;postalCode/&amp;gt;
        &amp;lt;state/&amp;gt;
        &amp;lt;country/&amp;gt;
        &amp;lt;countryName/&amp;gt;
      &amp;lt;/address&amp;gt;
      &amp;lt;contact/&amp;gt;
    &amp;lt;/seller&amp;gt;
    &amp;lt;customerAccount accountTerminated=&quot;false&quot; code=&quot;CA_2000000021&quot; currency=&quot;EUR&quot;
        description=&quot;Edward&#39;s Account&quot; externalRef1=&quot;&quot; externalRef2=&quot;&quot; id=&quot;566&quot; jobTitle=&quot;&quot;
        language=&quot;English&quot; vatNo=&quot;xxx&quot;&amp;gt;
      &amp;lt;contact email=&quot;czetsuya@gmail.com&quot; fax=&quot;&quot; mobile=&quot;&quot; phone=&quot;(303) 829-5342&quot;/&amp;gt;
      &amp;lt;paymentMethod type=&quot;CHECK&quot;/&amp;gt;
      &amp;lt;name&amp;gt;
        &amp;lt;quality/&amp;gt;
        &amp;lt;name&amp;gt;Edward&#39;s Account&amp;lt;/name&amp;gt;
      &amp;lt;/name&amp;gt;
      &amp;lt;address&amp;gt;
        &amp;lt;address1&amp;gt;1234 Balamb Garden&amp;lt;/address1&amp;gt;
        &amp;lt;address2/&amp;gt;
        &amp;lt;address3/&amp;gt;
        &amp;lt;city&amp;gt;Los Banos&amp;lt;/city&amp;gt;
        &amp;lt;postalCode&amp;gt;4030&amp;lt;/postalCode&amp;gt;
        &amp;lt;state/&amp;gt;
        &amp;lt;country&amp;gt;PH&amp;lt;/country&amp;gt;
        &amp;lt;countryName&amp;gt;Philippines&amp;lt;/countryName&amp;gt;
      &amp;lt;/address&amp;gt;
    &amp;lt;/customerAccount&amp;gt;
    &amp;lt;billingAccount billingCycleCode=&quot;MONTHLY_POST_PAID&quot; code=&quot;BA_2000000021&quot;
        description=&quot;BA_2000000021&quot; endPeriod=&quot;31/05/2024&quot; externalRef1=&quot;&quot; externalRef2=&quot;&quot; id=&quot;567&quot;
        jobTitle=&quot;&quot; startPeriod=&quot;09/05/2024&quot;&amp;gt;
      &amp;lt;billingCycle code=&quot;MONTHLY_POST_PAID&quot; description=&quot;Monthly Invoice Cycle&quot; id=&quot;6&quot;&amp;gt;
      &amp;lt;/billingCycle&amp;gt;
      &amp;lt;email&amp;gt;czetsuya@gmail.com&amp;lt;/email&amp;gt;
      &amp;lt;name&amp;gt;
        &amp;lt;quality/&amp;gt;
        &amp;lt;name&amp;gt;Edward&#39;s Account&amp;lt;/name&amp;gt;
      &amp;lt;/name&amp;gt;
      &amp;lt;address&amp;gt;
        &amp;lt;address1&amp;gt;1234 Balamb Garden&amp;lt;/address1&amp;gt;
        &amp;lt;address2/&amp;gt;
        &amp;lt;address3/&amp;gt;
        &amp;lt;city&amp;gt;Los Banos&amp;lt;/city&amp;gt;
        &amp;lt;postalCode&amp;gt;4030&amp;lt;/postalCode&amp;gt;
        &amp;lt;state/&amp;gt;
        &amp;lt;country&amp;gt;PH&amp;lt;/country&amp;gt;
        &amp;lt;countryName&amp;gt;Philippines&amp;lt;/countryName&amp;gt;
      &amp;lt;/address&amp;gt;
      &amp;lt;message/&amp;gt;
      &amp;lt;billTo&amp;gt;
        &amp;lt;![CDATA[Edward&#39;s Account&amp;lt;br/&amp;gt;1234 Balamb Garden&amp;lt;br/&amp;gt;Los Banos, Laguna 4030&amp;lt;br/&amp;gt;PH]]&amp;gt;
      &amp;lt;/billTo&amp;gt;
      &amp;lt;fees/&amp;gt;
      &amp;lt;charges licensePlate=&quot;NLEDW_LE2021&quot;
          urlImage=&quot;https://czetsuyatech.com/PHEDW_LE2021.latest.jpg&quot;&amp;gt;
        &amp;lt;charge amount=&quot;-161.000000000000&quot; date=&quot;05/28/24 19:45&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/9/24 9:29 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566170&quot;/&amp;gt;
        &amp;lt;charge amount=&quot;161.000000000000&quot; date=&quot;5/10/24 7:52 AM&quot; description=&quot;GoPlus&quot;
            transactionId=&quot;566171&quot;/&amp;gt;
      &amp;lt;/charges&amp;gt;
      &amp;lt;topups/&amp;gt;
    &amp;lt;/billingAccount&amp;gt;
    &amp;lt;invoiceDate&amp;gt;13/05/2024&amp;lt;/invoiceDate&amp;gt;
    &amp;lt;dueDate&amp;gt;13/06/2024&amp;lt;/dueDate&amp;gt;
    &amp;lt;paymentMethod&amp;gt;CHECK&amp;lt;/paymentMethod&amp;gt;
    &amp;lt;comment&amp;gt;
    &amp;lt;/comment&amp;gt;
    &amp;lt;categories&amp;gt;
      &amp;lt;category code=&quot;CLASSIC_USAGE&quot; label=&quot;Service Usage&quot;&amp;gt;
        &amp;lt;amountWithoutTax&amp;gt;322.000000000000&amp;lt;/amountWithoutTax&amp;gt;
        &amp;lt;subCategories&amp;gt;
          &amp;lt;subCategory amountWithoutTax=&quot;322.000000000000&quot; code=&quot;CLASSIC_USAGE&quot;
              label=&quot;Service Usage&quot; taxCode=&quot;TAX_00&quot; taxPercent=&quot;0.000000000000&quot;/&amp;gt;
        &amp;lt;/subCategories&amp;gt;
      &amp;lt;/category&amp;gt;
    &amp;lt;/categories&amp;gt;
    &amp;lt;discounts/&amp;gt;
    &amp;lt;locale&amp;gt;en-US&amp;lt;/locale&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;amount&amp;gt;
    &amp;lt;currency&amp;gt;EUR&amp;lt;/currency&amp;gt;
    &amp;lt;amountWithoutTax&amp;gt;322.000000000000&amp;lt;/amountWithoutTax&amp;gt;
    &amp;lt;amountWithTax&amp;gt;322.000000000000&amp;lt;/amountWithTax&amp;gt;
    &amp;lt;netToPay&amp;gt;322.000000000000&amp;lt;/netToPay&amp;gt;
    &amp;lt;taxes total=&quot;0.000000000000&quot;&amp;gt;
      &amp;lt;tax code=&quot;TAX_00&quot; id=&quot;1&quot;&amp;gt;
        &amp;lt;name&amp;gt;0 Percent Tax&amp;lt;/name&amp;gt;
        &amp;lt;percent&amp;gt;0.000000000000&amp;lt;/percent&amp;gt;
        &amp;lt;amount&amp;gt;0.000000000000&amp;lt;/amount&amp;gt;
        &amp;lt;amountHT&amp;gt;322.000000000000&amp;lt;/amountHT&amp;gt;
      &amp;lt;/tax&amp;gt;
    &amp;lt;/taxes&amp;gt;
  &amp;lt;/amount&amp;gt;
  &amp;lt;detail&amp;gt;
    &amp;lt;userAccounts&amp;gt;
      &amp;lt;userAccount code=&quot;UA_2000000021&quot; description=&quot;UA_2000000021&quot; id=&quot;568&quot; jobTitle=&quot;&quot;&amp;gt;
        &amp;lt;customFields&amp;gt;
          &amp;lt;customField code=&quot;CUSTOMER_NUMBER&quot; description=&quot;Customer Number&quot;&amp;gt;1190017814&amp;lt;/customField&amp;gt;
          &amp;lt;customField code=&quot;ACCOUNT_NUMBER&quot; description=&quot;Account Number&quot;&amp;gt;2000000021&amp;lt;/customField&amp;gt;
        &amp;lt;/customFields&amp;gt;
        &amp;lt;subscriptions&amp;gt;
          &amp;lt;subscription code=&quot;SUBS_2000000021_NLEDW_LE2021_1590017830&quot;
              description=&quot;SUBS_2000000021_NLEDW_LE2021_1590017830&quot; id=&quot;309&quot; offerCode=&quot;GoPlus&quot;&amp;gt;
            &amp;lt;subscriptionDate&amp;gt;13/10/2023&amp;lt;/subscriptionDate&amp;gt;
            &amp;lt;endAgreementDate/&amp;gt;
          &amp;lt;/subscription&amp;gt;
        &amp;lt;/subscriptions&amp;gt;
        &amp;lt;name&amp;gt;
          &amp;lt;quality/&amp;gt;
          &amp;lt;firstName&amp;gt;Ed&amp;lt;/firstName&amp;gt;
          &amp;lt;name/&amp;gt;
        &amp;lt;/name&amp;gt;
        &amp;lt;address&amp;gt;
          &amp;lt;address1/&amp;gt;
          &amp;lt;address2/&amp;gt;
          &amp;lt;address3/&amp;gt;
          &amp;lt;city/&amp;gt;
          &amp;lt;postalCode/&amp;gt;
          &amp;lt;state/&amp;gt;
          &amp;lt;country/&amp;gt;
          &amp;lt;countryName/&amp;gt;
        &amp;lt;/address&amp;gt;
        &amp;lt;categories&amp;gt;
          &amp;lt;category code=&quot;CLASSIC_USAGE&quot; label=&quot;Service Usage&quot;&amp;gt;
            &amp;lt;amountWithoutTax&amp;gt;322.000000000000&amp;lt;/amountWithoutTax&amp;gt;
            &amp;lt;subCategories&amp;gt;
              &amp;lt;subCategory amountWithoutTax=&quot;322.000000000000&quot; code=&quot;CLASSIC_USAGE&quot;
                  label=&quot;Service Usage&quot; taxCode=&quot;TAX_00&quot; taxPercent=&quot;0.000000000000&quot;&amp;gt;
                &amp;lt;line code=&quot;GoPlus_CT&quot;
                    param1=&quot;fad4c36b-215a-308d-a19d-b6074543d414:fad4c36b-215a-308d-a19d-b6074543d414&quot;
                    param2=&quot;1&quot; param3=&quot;566170&quot;&amp;gt;
                  &amp;lt;walletOperation code=&quot;GoPlus_CT&quot; periodEndDate=&quot;&quot; periodStartDate=&quot;&quot;/&amp;gt;
                  &amp;lt;paramExtra&amp;gt;USAGE&amp;lt;/paramExtra&amp;gt;
                  &amp;lt;pricePlan code=&quot;GoPlus_CT&quot; description=&quot;Price Plan for GoPlus CT&quot;/&amp;gt;
                  &amp;lt;label&amp;gt;
                    GoPlus||EUR|1.0|0.00|161.00|161.00|0E-12|161.000000000000|161.000000000000|USE_TRX_PRICE
                  &amp;lt;/label&amp;gt;
                  &amp;lt;unitAmountWithoutTax&amp;gt;161.000000000000&amp;lt;/unitAmountWithoutTax&amp;gt;
                  &amp;lt;amountWithoutTax&amp;gt;161.000000000000&amp;lt;/amountWithoutTax&amp;gt;
                  &amp;lt;quantity&amp;gt;1.000000000000&amp;lt;/quantity&amp;gt;
                  &amp;lt;usageDate&amp;gt;09/05/2024&amp;lt;/usageDate&amp;gt;
                  &amp;lt;edr accessCode=&quot;AP_2000000021_NLEDW_LE2021&quot; dateParam1=&quot;&quot; dateParam2=&quot;&quot;
                      dateParam3=&quot;&quot; dateParam4=&quot;&quot; dateParam5=&quot;&quot; decimalParam1=&quot;161.000000000000&quot;
                      decimalParam2=&quot;&quot; decimalParam3=&quot;&quot; decimalParam4=&quot;&quot; decimalParam5=&quot;&quot;
                      eventDate=&quot;2024-05-09T09:29:46&quot; originBatch=&quot;API_192.168.67.123&quot;
                      originRecord=&quot;opencell.admin_1715247534640&quot;
                      parameter1=&quot;fad4c36b-215a-308d-a19d-b6074543d414:fad4c36b-215a-308d-a19d-b6074543d414&quot;
                      parameter2=&quot;1&quot; parameter3=&quot;566170&quot; parameter4=&quot;CHARGE_GoPlus_CT&quot;
                      parameter5=&quot;VTOLL&quot; parameter6=&quot;NLEDW_LE2021&quot; parameter7=&quot;&quot;
                      parameter8=&quot;USE_TRX_PRICE&quot; parameter9=&quot;&quot; quantity=&quot;1.000000000000&quot;
                      rejectReason=&quot;&quot; status=&quot;RATED&quot;
                      subscription=&quot;SUBS_2000000021_NLEDW_LE2021_1590017830&quot;/&amp;gt;
                &amp;lt;/line&amp;gt;
                &amp;lt;line code=&quot;GoPlus_CT&quot;
                    param1=&quot;fad4c36b-215a-308d-a19d-b6074543d414:fad4c36b-215a-308d-a19d-b6074543d414&quot;
                    param2=&quot;1&quot; param3=&quot;566171&quot;&amp;gt;
                  &amp;lt;walletOperation code=&quot;GoPlus_CT&quot; periodEndDate=&quot;&quot; periodStartDate=&quot;&quot;/&amp;gt;
                  &amp;lt;paramExtra&amp;gt;USAGE&amp;lt;/paramExtra&amp;gt;
                  &amp;lt;pricePlan code=&quot;GoPlus_CT&quot; description=&quot;Price Plan for GoPlus CT&quot;/&amp;gt;
                  &amp;lt;label&amp;gt;
                    GoPlus||EUR|1.0|0.00|161.00|161.00|0E-12|161.000000000000|161.000000000000|USE_TRX_PRICE
                  &amp;lt;/label&amp;gt;
                  &amp;lt;unitAmountWithoutTax&amp;gt;161.000000000000&amp;lt;/unitAmountWithoutTax&amp;gt;
                  &amp;lt;amountWithoutTax&amp;gt;161.000000000000&amp;lt;/amountWithoutTax&amp;gt;
                  &amp;lt;quantity&amp;gt;1.000000000000&amp;lt;/quantity&amp;gt;
                  &amp;lt;usageDate&amp;gt;10/05/2024&amp;lt;/usageDate&amp;gt;
                  &amp;lt;edr accessCode=&quot;AP_2000000021_NLEDW_LE2021&quot; dateParam1=&quot;&quot; dateParam2=&quot;&quot;
                      dateParam3=&quot;&quot; dateParam4=&quot;&quot; dateParam5=&quot;&quot; decimalParam1=&quot;161.000000000000&quot;
                      decimalParam2=&quot;&quot; decimalParam3=&quot;&quot; decimalParam4=&quot;&quot; decimalParam5=&quot;&quot;
                      eventDate=&quot;2024-05-10T07:52:12&quot; originBatch=&quot;API_192.168.67.60&quot;
                      originRecord=&quot;opencell.admin_1715327638732&quot;
                      parameter1=&quot;fad4c36b-215a-308d-a19d-b6074543d414:fad4c36b-215a-308d-a19d-b6074543d414&quot;
                      parameter2=&quot;1&quot; parameter3=&quot;566171&quot; parameter4=&quot;CHARGE_GoPlus_CT&quot;
                      parameter5=&quot;VTOLL&quot; parameter6=&quot;NLEDW_LE2021&quot; parameter7=&quot;&quot;
                      parameter8=&quot;USE_TRX_PRICE&quot; parameter9=&quot;&quot; quantity=&quot;1.000000000000&quot;
                      rejectReason=&quot;&quot; status=&quot;RATED&quot;
                      subscription=&quot;SUBS_2000000021_NLEDW_LE2021_1590017830&quot;/&amp;gt;
                &amp;lt;/line&amp;gt;
              &amp;lt;/subCategory&amp;gt;
            &amp;lt;/subCategories&amp;gt;
          &amp;lt;/category&amp;gt;
        &amp;lt;/categories&amp;gt;
      &amp;lt;/userAccount&amp;gt;
      &amp;lt;userAccount description=&quot;-&quot;&amp;gt;
        &amp;lt;categories/&amp;gt;
      &amp;lt;/userAccount&amp;gt;
    &amp;lt;/userAccounts&amp;gt;
  &amp;lt;/detail&amp;gt;
  &amp;lt;offers&amp;gt;
    &amp;lt;offer code=&quot;GoPlus&quot; description=&quot;GoPlus&quot; id=&quot;1&quot;&amp;gt;
      &amp;lt;customFields&amp;gt;
        &amp;lt;customField code=&quot;CF_SHARED&quot; description=&quot;Offer Shared&quot;&amp;gt;INDIVIDUAL&amp;lt;/customField&amp;gt;
      &amp;lt;/customFields&amp;gt;
    &amp;lt;/offer&amp;gt;
  &amp;lt;/offers&amp;gt;
  &amp;lt;services&amp;gt;
    &amp;lt;service code=&quot;CT&quot; description=&quot;CT&quot; offerCode=&quot;GoPlus&quot;/&amp;gt;
  &amp;lt;/services&amp;gt;
  &amp;lt;priceplans&amp;gt;
    &amp;lt;priceplan code=&quot;GoPlus_CT&quot; description=&quot;Price Plan for GoPlus CT&quot;&amp;gt;
    &amp;lt;/priceplan&amp;gt;
  &amp;lt;/priceplans&amp;gt;
  &amp;lt;orders/&amp;gt;
  &amp;lt;qrCode
      value=&quot;xxx&quot;/&amp;gt;
  &amp;lt;invoiceQRCodeEmail
      value=&quot;xxx&quot;/&amp;gt;
  &amp;lt;summary currentBalance=&quot;-644.00&quot; outstandingBalance=&quot;0.00&quot; previousBalance=&quot;0.00&quot;
      totalAmountDue=&quot;322.00&quot;/&amp;gt;
&amp;lt;/invoice&amp;gt;
		
		&lt;br /&gt;
	&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;div style=&quot;text-align: left;&quot;&gt;As we can see the root of our document is 
		
		&lt;b&gt;
			&lt;i&gt;&quot;invoice&quot;&lt;/i&gt;
		&lt;/b&gt;.
	
	&lt;/div&gt;
	&lt;h3 style=&quot;text-align: left;&quot;&gt;3.2 Jasper Dependencies&lt;/h3&gt;
	&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;net.sf.jasperreports&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jasperreports&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;6.21.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;jaxen&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jaxen&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.2.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.apache.groovy&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;groovy&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;4.0.21&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;net.sf.jasperreports&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jasperreports-fonts&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;6.20.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;h3 style=&quot;text-align: left;&quot;&gt;3.3 Java Code&lt;/h3&gt;
	&lt;pre class=&quot;brush: java&quot;&gt;package com.czetsuyatech;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRXmlDataSource;
import net.sf.jasperreports.engine.util.JRLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

public class InvoiceReportGenerator {

  private final String jasperReportsPath;
  private ClassLoader cl;
  private String INVOICE_TAG_NAME = &quot;invoice&quot;;

  public InvoiceReportGenerator(String jasperReportsPath, URLClassLoader cl) {

    this.jasperReportsPath = jasperReportsPath;
    this.cl = cl;
  }

  public void generate(String jasperPath, String xmlDataSource)
      throws IOException, ParserConfigurationException, SAXException, TransformerException, JRException {

    File jasperFile = new File(jasperReportsPath, jasperPath);
    String pdfFullFilename = jasperReportsPath + &quot;/output.pdf&quot;;
    File invoiceXmlFile = new File(jasperReportsPath, xmlDataSource);
    Map&amp;lt;String, Object&amp;gt; parameters = getParameters();

    InputStream reportTemplate = new FileInputStream(jasperFile);
    Node invoiceNode = getInvoiceNode(invoiceXmlFile);

    JRXmlDataSource dataSource = new JRXmlDataSource(getJasperReportContext(invoiceNode), &quot;/&quot; + INVOICE_TAG_NAME);

    Map&amp;lt;String, JasperReport&amp;gt; jasperReportMap = new HashMap&amp;lt;&amp;gt;();

    String fileKey = jasperFile.getPath() + jasperFile.lastModified();
    JasperReport jasperReport = jasperReportMap.get(fileKey);
    if (jasperReport == null) {
      jasperReport = (JasperReport) JRLoader.loadObject(reportTemplate);
      jasperReportMap.put(fileKey, jasperReport);
    }

    DefaultJasperReportsContext context = DefaultJasperReportsContext.getInstance();
    JRPropertiesUtil.getInstance(context).setProperty(&quot;net.sf.jasperreports.xpath.executer.factory&quot;,
        &quot;net.sf.jasperreports.engine.util.xml.JaxenXPathExecuterFactory&quot;);

    JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);

    JasperExportManager.exportReportToPdfFile(jasperPrint, pdfFullFilename);
  }

  private Node getInvoiceNode(File invoiceXmlFile)
      throws TransformerException, ParserConfigurationException, IOException, SAXException {

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    dbf.setNamespaceAware(true);
    Document xmlDocument = db.parse(invoiceXmlFile);
    xmlDocument.getDocumentElement().normalize();
    Node invoiceNode = xmlDocument.getElementsByTagName(INVOICE_TAG_NAME).item(0);
    Transformer trans = TransformerFactory.newInstance().newTransformer();
    trans.setOutputProperty(OutputKeys.INDENT, &quot;yes&quot;);
    StringWriter writer = new StringWriter();
    trans.transform(new DOMSource(xmlDocument), new StreamResult(writer));

    return invoiceNode;
  }

  private ByteArrayInputStream getJasperReportContext(Node invoiceNode) throws TransformerException {

    return new ByteArrayInputStream(getNodeXmlString(invoiceNode).getBytes(
        StandardCharsets.UTF_8));
  }

  private Map&amp;lt;String, Object&amp;gt; getParameters() {

    return new HashMap&amp;lt;&amp;gt;() {{
      put(JRParameter.REPORT_CLASS_LOADER, cl);
    }};
  }

  protected String getNodeXmlString(Node node) throws TransformerException {

    TransformerFactory transFactory = TransformerFactory.newInstance();
    Transformer transformer = transFactory.newTransformer();
    StringWriter buffer = new StringWriter();
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, &quot;yes&quot;);
    transformer.transform(new DOMSource(node), new StreamResult(buffer));

    return buffer.toString();
  }
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Resources&lt;/h2&gt;
	The complete source code is available at GitHub:		
	&lt;a href=&quot;https://github.com/czetsuyatech/jasperreports-xml-datasource&quot;&gt;https://github.com/czetsuyatech/jasperreports-xml-datasource&lt;/a&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2&gt;5. Development and Support&lt;/h2&gt;&lt;div&gt;Unlock the full coding experience! As a&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot; target=&quot;_blank&quot;&gt;GitHub Sponsor&lt;/a&gt;, you gain exclusive access to the code behind this article—start learning and building today!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;m available for contracting services and support. You can reach me at:&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/111934355531639029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/111934355531639029?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/111934355531639029'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/111934355531639029'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/06/java-how-to-create-jasper-report-from-xml-datasource.html' title='How to Create a Jasper Report from an XML Datasource: A Step-by-Step Guide'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-8271294245588918610</id><published>2024-04-20T18:03:00.017+08:00</published><updated>2025-03-30T21:41:47.026+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-rest"/><title type='text'>Hands-on Coding: Spring Boot Common Exceptions Handling</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Overview&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;What is an exception?&lt;/p&gt;
  &lt;p&gt;An exception is an unexpected event, behavior, or state during software execution. In Java, it is a subclass of java.lang.Throwable.&lt;/p&gt;&lt;p&gt;In this diagram where a user withdraws money from an account the system throws an exception when the amount is less than or equal to the account balance.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivoUHEaPofu8qF1wfsSFYY9ZyA5O3VLKa9SQs37sGBr4jLk8c9jc03P6Qr_AILj0Mha4ME4oz_8Bc4GgK4c7n7xgddNw2r-Hwe5dI_njLH8VHZw77KUDduiCoPvQL1favnIVgIjQCz-APQ14Qcygg0ss6UQ_0BODKwK41eloYS4TQBldFEDpUUF5QcOmvR/s471/Basic%20Exceptions.drawio.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;471&quot; data-original-width=&quot;291&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivoUHEaPofu8qF1wfsSFYY9ZyA5O3VLKa9SQs37sGBr4jLk8c9jc03P6Qr_AILj0Mha4ME4oz_8Bc4GgK4c7n7xgddNw2r-Hwe5dI_njLH8VHZw77KUDduiCoPvQL1favnIVgIjQCz-APQ14Qcygg0ss6UQ_0BODKwK41eloYS4TQBldFEDpUUF5QcOmvR/s16000/Basic%20Exceptions.drawio.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;b&gt;Exception Class Diagram&lt;/b&gt;&lt;/p&gt;
  &lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
    &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9pBOF9PpNK_uZludnYZ9QcV3_mx505AITAT_AaQyUrh_vcwLuz0R28TmDLJD0GkLeY5VJBVL694B1A__HLNlBvTvyuANvly7ZHLboPP1Xs9mbz7aVNsKJahpbdll0GgRbxlkzHBTAHh56rQgyPaBsUYL9QuYTYTaiNe7SXWcWZBCp4ni4Ptcu8W9IJSff/s901/java_lang_exception.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
      &lt;img border=&quot;0&quot; data-original-height=&quot;531&quot; data-original-width=&quot;901&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9pBOF9PpNK_uZludnYZ9QcV3_mx505AITAT_AaQyUrh_vcwLuz0R28TmDLJD0GkLeY5VJBVL694B1A__HLNlBvTvyuANvly7ZHLboPP1Xs9mbz7aVNsKJahpbdll0GgRbxlkzHBTAHh56rQgyPaBsUYL9QuYTYTaiNe7SXWcWZBCp4ni4Ptcu8W9IJSff/s16000/java_lang_exception.png&quot; /&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;br /&gt;
  &lt;p&gt;In this diagram, we can see two types of Throwable.&amp;nbsp;&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;ol style=&quot;text-align: left;&quot;&gt;
    &lt;li&gt;Exception - recoverable&lt;/li&gt;
    &lt;li&gt;Error - unrecoverable&lt;/li&gt;
  &lt;/ol&gt;
  &lt;div&gt;
    &lt;b&gt;Exception VS RuntimeException&lt;/b&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;Exception - we need to declare the thrown exception in the method signature&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;public String readFromInputStream(InputStream inputStream) throws IOException {
  StringBuilder resultStringBuilder = new StringBuilder();
  try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
    String line;
    // BufferedReader.readLine throws IOException
    while ((line = br.readLine()) != null) {
      resultStringBuilder.append(line).append(&quot;\n&quot;);
    }
  }
  return resultStringBuilder.toString();
}
  &lt;/pre&gt;
  &lt;p&gt;RuntimeException - there is no need to add the exception in the method signature&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;public void rtException() {
  int arr[] = null; // null array
  System.out.println(&quot;NullPointerException: &quot; + arr.length);
}
  &lt;/pre&gt;
  &lt;p&gt;In the calling block, we can catch the exception that was thrown.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;try {
  readFromInputStream(is);
  rtException()
  
} catch (IOException ioe) {
  //
} catch (NullPointerException npe) {
  //  
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Pre-Spring Boot 3&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;Before Spring Boot 3 there are several approaches on how we handle the exceptions. This article is opinionated so I will just list them for reference.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;@ExceptionHandler annotation inside a controller&lt;/li&gt;
    &lt;li&gt;Implement HandlerExceptionResolver or extend AbstractHandlerExceptionResolver&lt;/li&gt;
    &lt;li&gt;Extending ResponseStatusException&lt;/li&gt;
    &lt;li&gt;Zalando exception handling library&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Microservice&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;Microservice is a software architecture that builds products as collections of small services. Often, these services are powered by Spring Boot, and they interact with each other via HTTP calls through REST endpoints. Before Spring Boot 3, developers often used Zalando to wrap and handle exceptions, but Spring patched this gap and now provides an elegant solution.&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyFJgLhLdRyxNPxPC_FnWgIXCd8x5BWf2wWPlojZM29tjt9M9UWk3SMpjXxOmoTIS7x5mHVroNbiCJ8HpLtO3dg4jPOynHxw1IVpAgLtggOVOXMj9xXf3c-Uyo0U_Xfh7vPoCKFfKq4FiycXGBOkCTaFa8JDMaBuoNyNxyYCGEkaFW6RJxP321PKR6oIZN/s539/Microservice_Architecture.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;370&quot; data-original-width=&quot;539&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyFJgLhLdRyxNPxPC_FnWgIXCd8x5BWf2wWPlojZM29tjt9M9UWk3SMpjXxOmoTIS7x5mHVroNbiCJ8HpLtO3dg4jPOynHxw1IVpAgLtggOVOXMj9xXf3c-Uyo0U_Xfh7vPoCKFfKq4FiycXGBOkCTaFa8JDMaBuoNyNxyYCGEkaFW6RJxP321PKR6oIZN/s16000/Microservice_Architecture.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;https://microservices.io/patterns/microservices.html&lt;/p&gt;&lt;p&gt;In this diagram, we can see that the API gateway calls other services. And internal services can also call each other. During these calls it’s possible to encounter exceptions. That’s why it’s important to have a common library that we can add as a dependency and customize the errors based on the domain.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Commons Web Exceptions&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;Commons Web Exceptions is a project I created in Spring Boot 3, it is built around:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;RestControllerAdvice - for centralizing the exceptions&lt;/li&gt;
    &lt;li&gt;ResponseEntityExceptionHandler - for providing the basic exception information structure&lt;/li&gt;
    &lt;li&gt;ErrorResponseException - for initializing an exception&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1iGv2csgY8eYRx5io7zu6Ius3sSi6WlckOqXyOV031Z62swldMXGQM1cHZDs6slJmmmJkgJrWMi3r1TQl1XXNJjbiZglak1jvkyRUFlt2yWJ5q7b6uKRgSJ6495m5eJNUnGqcL7zv0H_nrNDm5jXgUlopPBLzeQHY70QdKy39tSw2kaP2O-bP9BcpRNiX/s1161/class-diagram.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;511&quot; data-original-width=&quot;1161&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1iGv2csgY8eYRx5io7zu6Ius3sSi6WlckOqXyOV031Z62swldMXGQM1cHZDs6slJmmmJkgJrWMi3r1TQl1XXNJjbiZglak1jvkyRUFlt2yWJ5q7b6uKRgSJ6495m5eJNUnGqcL7zv0H_nrNDm5jXgUlopPBLzeQHY70QdKy39tSw2kaP2O-bP9BcpRNiX/s16000/class-diagram.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;In this image, all the classes and enums inside the blue block are part of the commons web exceptions library that we can use and extend in our service.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;b&gt;NativeWebExceptionEnumCodes&lt;/b&gt; - is an enum where native exceptions such as BAD_REQUEST, and INVALID_FORMAT are defined. In here, we can also add the class name of an exception in case we want to provide a specific code. Eg. HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;AbstractWebExceptions &lt;/b&gt;- is a container for all the native and service-defined exceptions that we can register by extending this class.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;WebBaseException &lt;/b&gt;- a model class that extends ErrorResponseException. It provides the basic structure of the exception.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;AbstractWebExceptionHandler &lt;/b&gt;- which extends the ResponseEntityExceptionHandler class. It gives us default handlers for the most common exceptions such as HttpRequestMethodNotSupportedException, HttpMediaTypeNotSupportedException, and HttpMediaTypeNotAcceptableException. In this class, we override some methods that will allow us to provide custom decorations to our exceptions. For example, when handling the invalid method argument exception we can list the errors (eg notNull) in the custom property &quot;ERRORS&quot;.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Usage&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;In this section, I will provide some examples of how we can use this library.&lt;/p&gt;
 &lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Initializing the Library&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;Before we can define our custom exception handlers, we should extend the necessary base classes first.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;1. Define the service&#39;s exception codes as enum. For example, in one of the services I have in
    Hivemaster, a custom Keycloak project that provides a multi-tenant feature.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;@Getter
public enum AppExceptionCodes {

  USER_CREATION_FAILED(&quot;A1001&quot;, &quot;Use creation failed&quot;),
  USER_EID_NOT_FOUND(&quot;A1002&quot;, &quot;User EID not found&quot;),
  USER_EMAIL_NOT_FOUND(&quot;A1003&quot;, &quot;User email not found&quot;),
  USER_PHONE_NOT_FOUND(&quot;A1004&quot;, &quot;User phone not found&quot;),
  ORGANIZATION_NOT_FOUND(&quot;A1005&quot;, &quot;Organization not found&quot;);

  private String code;
  private String message;

  AppExceptionCodes(String code, String message) {
    this.code = code;
    this.message = message;
  }

  public static Map&amp;lt;String, String&amp;gt; getMapValues() {

    Map&amp;lt;String, String&amp;gt; map = new LinkedHashMap&amp;lt;&amp;gt;();
    for (AppExceptionCodes errCode : values()) {
      map.put(errCode.getCode(), errCode.getMessage());
    }

    return map;
  }
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;2. Register the exception codes by extending the class AbstractWebExceptionCodes, so that they can be accessed by the commons-exception project.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;@Component
public class WebExceptions extends AbstractWebExceptions {

  @Value(&quot;${spring.application.name}&quot;)
  private String serviceName;

  public WebExceptions() {

    super(HttpStatus.OK);

    registerExceptionMap(AppExceptionCodes.getMapValues());
  }

  @Override
  public String getServiceName() {
    return serviceName;
  }
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;3. Extend the WebBaseException class. So that we can handle business exceptions specific to the service. This class extends ErrorResponseException and RuntimeException which should be extended by the service exception classes. This will allow us to override the decoration that happens on the base WebBaseException class.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;public class WebException extends WebBaseException {

  public WebException(HttpStatusCode status, String code) {
    this(status, code, null);
  }

  public WebException(HttpStatusCode status, String code, String message) {
    super(status, code, message);
  }
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;4. Extend the base exception handler AbstractWebExceptionHandler, which provides custom error handling and decoration for exceptions like method argument, runtime, invalid format, etc.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class WebExceptionHandler extends AbstractWebExceptionHandler {

  private final WebExceptions webExceptions;

  @Override
  public String getServiceName() {
    return webExceptions.getServiceName();
  }
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;5. And finally, import the library into your project.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;@EnableConfigurationProperties
@SpringBootApplication
@Import({WebExceptionHandlerConfig.class})
public class Application {}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Use Cases&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;Once all of these are done, we can now begin customizing our exceptions.&lt;/p&gt;  
&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;1. Handling native exceptions such as method invalid argument.&lt;/p&gt;
  &lt;p&gt;Here are the possible results that we may get:&lt;/p&gt;
  &lt;p&gt;Using custom assertion&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S404&quot;,
    &quot;title&quot;: &quot;Bad Request&quot;,
    &quot;status&quot;: 400,
    &quot;detail&quot;: &quot;Validation failed for fields (object:userV1, field:emailOrPhoneOnly, message:AssertTrue)&quot;,
    &quot;instance&quot;: &quot;/api/native-exceptions/method-arguments&quot;,
    &quot;code&quot;: &quot;S404&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:41:21.660650900Z&quot;,
    &quot;errors&quot;: [
        &quot;object:userV1, field:emailOrPhoneOnly, message:AssertTrue&quot;
    ]
}
  &lt;/pre&gt;
  &lt;p&gt;Missing field&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S404&quot;,
    &quot;title&quot;: &quot;Bad Request&quot;,
    &quot;status&quot;: 400,
    &quot;detail&quot;: &quot;Validation failed for fields (object:userV1, field:organization, message:NotNull)&quot;,
    &quot;instance&quot;: &quot;/api/native-exceptions/method-arguments&quot;,
    &quot;code&quot;: &quot;S404&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:43:25.418080500Z&quot;,
    &quot;errors&quot;: [
        &quot;object:userV1, field:organization, message:NotNull&quot;
    ]
}
  &lt;/pre&gt;
  &lt;p&gt;Invalid format&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S401&quot;,
    &quot;title&quot;: &quot;Bad Request&quot;,
    &quot;status&quot;: 400,
    &quot;detail&quot;: &quot;Invalid format for (field: birthdate, value: xxx, type: Instant)&quot;,
    &quot;instance&quot;: &quot;/api/native-exceptions/method-arguments&quot;,
    &quot;code&quot;: &quot;S401&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:43:44.065740700Z&quot;,
    &quot;errors&quot;: [
        &quot;field:birthdate, value:xxx, type:Instant&quot;
    ]
}
  &lt;/pre&gt;
  &lt;p&gt;Invalid date&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S404&quot;,
    &quot;title&quot;: &quot;Bad Request&quot;,
    &quot;status&quot;: 400,
    &quot;detail&quot;: &quot;Validation failed for fields (object:userV1, field:birthdate, message:Past)&quot;,
    &quot;instance&quot;: &quot;/api/native-exceptions/method-arguments&quot;,
    &quot;code&quot;: &quot;S404&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:44:28.829450600Z&quot;,
    &quot;errors&quot;: [
        &quot;object:userV1, field:birthdate, message:Past&quot;
    ]
}
  &lt;/pre&gt;
  &lt;p&gt;Missing resource&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S405&quot;,
    &quot;title&quot;: &quot;Not Found&quot;,
    &quot;status&quot;: 404,
    &quot;detail&quot;: &quot;No static resource native-exceptions/resource-not-found.&quot;,
    &quot;instance&quot;: &quot;/api/native-exceptions/resource-not-found&quot;,
    &quot;code&quot;: &quot;S405&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:44:44.294238700Z&quot;,
    &quot;errors&quot;: []
}
  &lt;/pre&gt;
  &lt;p&gt;Unsupported method&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S402&quot;,
    &quot;title&quot;: &quot;Method Not Allowed&quot;,
    &quot;status&quot;: 405,
    &quot;detail&quot;: &quot;Method &#39;PUT&#39; is not supported.&quot;,
    &quot;instance&quot;: &quot;/api/native-exceptions/method-arguments&quot;,
    &quot;code&quot;: &quot;S402&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:45:00.240704900Z&quot;,
    &quot;errors&quot;: []
}
  &lt;/pre&gt;
  &lt;p&gt;Forbidden&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/S501&quot;,
    &quot;title&quot;: &quot;Forbidden&quot;,
    &quot;status&quot;: 403,
    &quot;instance&quot;: &quot;/api/native-exceptions/forbidden&quot;,
    &quot;code&quot;: &quot;S501&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:45:20.566494300Z&quot;,
    &quot;errors&quot;: []
}
  &lt;/pre&gt;
  &lt;p&gt;Exception defined in our service exception enum which could be business, application, or entity.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/A1001&quot;,
    &quot;title&quot;: &quot;Use creation failed&quot;,
    &quot;status&quot;: 400,
    &quot;instance&quot;: &quot;/api/service-exceptions/users/exceptions&quot;,
    &quot;code&quot;: &quot;A1001&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:55:49.061434900Z&quot;,
    &quot;errors&quot;: []
}
&lt;/pre&gt;
  &lt;p&gt;Custom exception&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;{
    &quot;type&quot;: &quot;http://localhost:8080/errors/B1001&quot;,
    &quot;title&quot;: &quot;Business exception&quot;,
    &quot;status&quot;: 400,
    &quot;detail&quot;: &quot;Custom exception message&quot;,
    &quot;instance&quot;: &quot;/api/service-exceptions/users/custom-exceptions&quot;,
    &quot;code&quot;: &quot;B1001&quot;,
    &quot;service&quot;: &quot;commons-web-exception-client&quot;,
    &quot;timestamp&quot;: &quot;2024-04-20T09:56:30.612646200Z&quot;,
    &quot;errors&quot;: []
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Git Repository&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;Once again the repositories are available in GitHub.&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/commons-web-exception&quot;&gt;https://github.com/czetsuyatech/commons-web-exception&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/commons-web-exception-client&quot;&gt;https://github.com/czetsuyatech/commons-web-exception-client&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;h2&gt;Development and Support&lt;/h2&gt;&lt;div&gt;Unlock the full coding experience! As a&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot; target=&quot;_blank&quot;&gt;GitHub Sponsor&lt;/a&gt;, you gain exclusive access to the code behind this article—start learning and building today!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;m available for contracting services and support. You can reach me at:&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/8271294245588918610/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/8271294245588918610?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/8271294245588918610'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/8271294245588918610'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/04/spring-boot-common-exceptions-handling.html' title='Hands-on Coding: Spring Boot Common Exceptions Handling'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivoUHEaPofu8qF1wfsSFYY9ZyA5O3VLKa9SQs37sGBr4jLk8c9jc03P6Qr_AILj0Mha4ME4oz_8Bc4GgK4c7n7xgddNw2r-Hwe5dI_njLH8VHZw77KUDduiCoPvQL1favnIVgIjQCz-APQ14Qcygg0ss6UQ_0BODKwK41eloYS4TQBldFEDpUUF5QcOmvR/s72-c/Basic%20Exceptions.drawio.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-2277630675979384350</id><published>2024-04-06T17:36:00.010+08:00</published><updated>2024-04-11T12:30:56.070+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="logging"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><title type='text'>Hands on Coding: Spring Logging for Beginners</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Overview&lt;/h2&gt;
&lt;div&gt;Logging is a vital aspect of programming for both beginners and experts, often underestimated but crucial for understanding application behavior. Strategic placement of logging statements aids in debugging and comprehending program execution, especially in production environments.&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;This tutorial is hands-on so I&#39;ll just paste the reference for you: &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging&quot;&gt;https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging&lt;/a&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Hands-On Coding&lt;/h2&gt;
&lt;div&gt;Logging is available out-of-the-box from Spring, but we can customize its level, format, etc as defined in the document above.&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;I created a Spring Boot project to capture the behavior of the logging levels that you can use as a reference as you code.&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.1 Common Logging Interface&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;Let&#39;s introduce an interface that we will implement with different logging-level classes. The method logLevels will print all the logs applicable to a particular package.&lt;/p&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;package com.czetsuyatech.logger;

import org.slf4j.Logger;

public interface LoggerComponent {

  default void logLevels() {
    System.out.println(getClass().getPackageName() + &quot;: &quot; + &quot;-&quot;.repeat(50));

    getLogger().info(&quot;Hello World&quot;);
    getLogger().debug(&quot;Hello World&quot;);
    getLogger().warn(&quot;Hello World&quot;);
    getLogger().trace(&quot;Hello World&quot;);
    getLogger().error(&quot;Hello World&quot;);
  }

  Logger getLogger();
}
  &lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.2 Configure Package Names&lt;/h3&gt;
&lt;p&gt;We will define the following packages.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;debug&lt;/li&gt;
  &lt;li&gt;error&lt;/li&gt;
  &lt;li&gt;info&lt;/li&gt;
  &lt;li&gt;trace&lt;/li&gt;
  &lt;li&gt;warn&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For each package, we need to define a concrete class of interface LoggerComponent&lt;/p&gt;
&lt;p&gt;For example, in the debug we will have something like:&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@Component
@Slf4j
public class DebugLogger implements LoggerComponent {

  @Override
  public Logger getLogger() {
    return log;
  }
}
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.3 Spring Boot Application Class&lt;/h3&gt;
&lt;p&gt;We will inject the instances of LoggerComponent in a list so that we can iterate through each component. We will call the logLevels method to print the log.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@SpringBootApplication
@RequiredArgsConstructor
public class Application {

  private final List&amp;lt;LoggerComponent&amp;gt; loggers;

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @EventListener
  public void onStartup(ContextRefreshedEvent event) {

    loggers.stream().forEach(LoggerComponent::logLevels);
  }
}
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.4 Spring Configuration File&lt;/h3&gt;
&lt;p&gt;And finally, we will configure the log level per package.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;logging:
  level:
    root: info
    com.czetsuyatech.logger.info: info
    com.czetsuyatech.logger.debug: debug
    com.czetsuyatech.logger.warn: warn
    com.czetsuyatech.logger.trace: trace
    com.czetsuyatech.logger.error: error
&lt;/pre&gt;
&lt;div&gt;We should have the following logs in our console which should pretty much explain the logging behavior. For example, error level only prints errors, while debug prints info, warn, and error as well.&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO-lTx0JSAuTVKS3475fcqGAoorfbJ7vzbvI8bSRVP6W_CFWEFBYJPYPp6Vsn4V6xXN1IKIFb4s7DLVOraCoZ1LPP1jyeO2LG96wC0EW1TKvWZYrN77szmhHsj445X4rOAbSgridKxe3Sj1c7kQT0Jx1TBRM7QGSWsSdvgToDZ5RdZskWicucRT2D7r0bZ/s2751/1-spring-log-levels.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;914&quot; data-original-width=&quot;2751&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO-lTx0JSAuTVKS3475fcqGAoorfbJ7vzbvI8bSRVP6W_CFWEFBYJPYPp6Vsn4V6xXN1IKIFb4s7DLVOraCoZ1LPP1jyeO2LG96wC0EW1TKvWZYrN77szmhHsj445X4rOAbSgridKxe3Sj1c7kQT0Jx1TBRM7QGSWsSdvgToDZ5RdZskWicucRT2D7r0bZ/s16000/1-spring-log-levels.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. When to Use Each Logger&lt;/h2&gt;
&lt;div&gt;
  There is no standard on how and when to use the different logging levels. In my own experience, this is how I use them. Coupled with proper package structure, so I can control the logging level of a particular process.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;Info&lt;/b&gt;&lt;/div&gt;&lt;div&gt;- Informational messages without state such as component startup and configuration settings.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Warn&lt;/b&gt;&lt;/div&gt;&lt;div&gt;- Informational state that can be ignored by the system.&amp;nbsp;&lt;/div&gt;&lt;div&gt;- Example: An unknown status of an entity.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Debug&lt;/b&gt;&lt;/div&gt;&lt;div&gt;- Informational events with state that are useful for developers. I use this in public methods.&lt;/div&gt;&lt;div&gt;- Example: Controller endpoints and service methods.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Trace&lt;/b&gt;&lt;/div&gt;&lt;div&gt;- Informational events with state that are useful for developers. I use this in private methods.&lt;/div&gt;&lt;div&gt;- Example: Private methods in a service.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Error&lt;/b&gt;&lt;/div&gt;&lt;div&gt;- Event that causes the application to get into an error state. Recoverable.&lt;/div&gt;&lt;div&gt;- Example: Unable to communicate to an external service. Having a mechanism to retry sending the message.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Fatal&lt;/b&gt;&lt;/div&gt;&lt;div&gt;- Event that causes the application to get into an error state. Unrecoverable.&lt;/div&gt;&lt;div&gt;- Example: Database instance going down.&lt;/div&gt;&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Code Repository&lt;/h2&gt;
&lt;div&gt;The code is available at&amp;nbsp; &lt;a href=&quot;https://github.com/czetsuya/lab-spring-logging&quot;&gt;https://github.com/czetsuya/lab-spring-logging&lt;/a&gt;. &lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/2277630675979384350/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/2277630675979384350?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2277630675979384350'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2277630675979384350'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/04/spring-logging-for-beginner.html' title='Hands on Coding: Spring Logging for Beginners'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO-lTx0JSAuTVKS3475fcqGAoorfbJ7vzbvI8bSRVP6W_CFWEFBYJPYPp6Vsn4V6xXN1IKIFb4s7DLVOraCoZ1LPP1jyeO2LG96wC0EW1TKvWZYrN77szmhHsj445X4rOAbSgridKxe3Sj1c7kQT0Jx1TBRM7QGSWsSdvgToDZ5RdZskWicucRT2D7r0bZ/s72-c/1-spring-log-levels.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-5258970771438068685</id><published>2024-04-06T10:53:00.007+08:00</published><updated>2025-03-30T21:50:44.636+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="grafana"/><category scheme="http://www.blogger.com/atom/ns#" term="prometheus"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><title type='text'>Hands on Coding: Spring Metrics with Prometheus for Beginner</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Overview&lt;/h2&gt;
&lt;p&gt;Welcome to our hands-on guide where we&#39;ll delve into the world of monitoring Spring Boot applications using Prometheus and Grafana. In today&#39;s fast-paced digital landscape, ensuring the smooth operation and performance of our applications is paramount. With the powerful combination of Prometheus and Grafana, we can gather insightful metrics and visualize them in a meaningful way, allowing us to monitor and optimize our Spring Boot applications effectively.&lt;/p&gt;
&lt;p&gt;In this guide, we&#39;ll walk through the process step-by-step, covering everything you need to know to set up basic monitoring for your Spring Boot 3 application with Docker.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Spring Boot 3 Application&lt;/h2&gt;
&lt;p&gt;The Spring Boot project contains the basic configuration needed for demonstration.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.1 Dependencies&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;We need the actuator to expose the Spring metrics and use the micrometer registry to convert it to Prometheus.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;micrometer-registry-prometheus&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.2 Configuration&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;In this section, we allow health and Prometheus endpoints from the actuator. By default, the actuator endpoint is/actuator, which lists all the enabled endpoints.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;logging:
  level:
    root: info

server:
  port: 8080

spring:
  application:
    name: lab-spring-prometheus

management:
  endpoints:
    web:
      exposure:
        include: health, prometheus
  endpoint:
    health:
      show-details: always
  prometheus:
    metrics:
      export:
        enabled: true
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Prometheus&lt;/h2&gt;
&lt;div&gt;
  &lt;p&gt;In Prometheus configuration, we need to specify the metrics path and set some labels that we will need in our Grafana dashboard later.&lt;/p&gt;
  &lt;p&gt;Note: If you want to scrape data from a Spring Boot app that runs on your IDE, you must change the targets to host.docker.internal. Don&#39;t forget to update the extra_hosts in the docker-compose file as well.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;scrape_configs:
  - job_name: &#39;lab-spring-prometheus&#39;
    metrics_path: &#39;/actuator/prometheus&#39;
    scrape_interval: 5s
    static_configs:
      #      - targets: [ &#39;host.docker.internal:8080&#39; ]
      - targets: [ &#39;spring-app:8080&#39; ]
        labels:
          namespace: czetsuyatech
          application: &#39;lab-spring-prometheus&#39;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Grafana&lt;/h2&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.1 Spring Boot Statistics&lt;/h3&gt;
&lt;p&gt;If you want a Spring Boot statistics dashboard out of the box you may check the following plugin in Grafana&#39;s marketplace. Or simply, import them.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;19004 -&amp;nbsp;https://grafana.com/grafana/dashboards/19004-spring-boot-statistics/&lt;/li&gt;
  &lt;li&gt;11378 -&amp;nbsp;https://grafana.com/grafana/dashboards/11378-justai-system-monitor/&lt;/li&gt;
&lt;/ul&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSDCqMC2stIM4t-rpwXKZ9hjyKJQmaD4qu1o3bJYDwqd6-zoi1Q65l7d07RmTAqEQmvMDGiLE0f_AfBnV1UAPEej1qMMrE2khaz_dzgVkf072N3QbC3RSvA1Zk4E2sv5oPl9hcAZBCNKyU_nWO1YL162KXjGu_jxK6eZDa1r8Gs_BvCJeZ9bgK5-FSQPHN/s3709/1-grafana-spring-dashboard.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1916&quot; data-original-width=&quot;3709&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSDCqMC2stIM4t-rpwXKZ9hjyKJQmaD4qu1o3bJYDwqd6-zoi1Q65l7d07RmTAqEQmvMDGiLE0f_AfBnV1UAPEej1qMMrE2khaz_dzgVkf072N3QbC3RSvA1Zk4E2sv5oPl9hcAZBCNKyU_nWO1YL162KXjGu_jxK6eZDa1r8Gs_BvCJeZ9bgK5-FSQPHN/s16000/1-grafana-spring-dashboard.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
  &lt;p&gt;For Grafana, we can start with a basic data source configuration. This points to the Prometheus URL we define in docker-compose. Note that you can also do this in the Grafana user interface.&lt;/p&gt;
  &lt;p&gt;Grafana&#39;s default username and password is &quot;admin&quot;.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;apiVersion: 1
datasources:
  - name: Lab Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;5. Docker Configuration&lt;/h2&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.1 Dockerfile&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;We are loading the Spring Boot jar to our docker container. Make sure to build the project first in your IDE, so that the jar is generated.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY ./target/*.jar app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.2 Docker Compose File&lt;/h3&gt;
&lt;div&gt;
  &lt;p&gt;This configuration runs our Spring boot app, Prometheus, and Grafana in docker with a common network. Thus, each service can access each other using the service name. If you want to access a non-docker component, let&#39;s say the Spring Boot app, you need to do the necessary configuration with the host.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;version: &#39;3.8&#39;

networks:
  backend:

services:
  spring-app:
    build:
      dockerfile: docker/Dockerfile
      context: ../
    ports:
      - &quot;8080:8080&quot;
    networks:
      - backend

  prometheus:
    image: prom/prometheus:v2.51.1
    container_name: lab-prometheus
    restart: no
    ports:
      - &quot;9090:9090&quot;
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - backend
#    extra_hosts:
#      - &#39;host.docker.internal:host-gateway&#39;

  grafana:
    image: grafana/grafana
    container_name: lab-grafana
    restart: no
    ports:
      - &quot;3000:3000&quot;
    volumes:
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    networks:
      - backend
&lt;/pre&gt;
  &lt;p&gt;
  To run, simply go to docker folder of this project and execute:

&lt;i&gt;docker-compose up --build
  &lt;/i&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;6. GitHub Repository&lt;/h2&gt;
&lt;p&gt;The code is available in GitHub including the docker-compose file.&lt;/p&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/lab-spring-prometheus&quot;&gt;https://github.com/czetsuyatech/lab-spring-prometheus&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;h2&gt;7. Development and Support&lt;/h2&gt;&lt;div&gt;Unlock the full coding experience! As a&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot; target=&quot;_blank&quot;&gt;GitHub Sponsor&lt;/a&gt;, you gain exclusive access to the code behind this article—start learning and building today!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;m available for contracting services and support. You can reach me at:&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/5258970771438068685/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/5258970771438068685?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5258970771438068685'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5258970771438068685'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/04/spring-metrics-with-prometheus-grafana-for-beginner.html' title='Hands on Coding: Spring Metrics with Prometheus for Beginner'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSDCqMC2stIM4t-rpwXKZ9hjyKJQmaD4qu1o3bJYDwqd6-zoi1Q65l7d07RmTAqEQmvMDGiLE0f_AfBnV1UAPEej1qMMrE2khaz_dzgVkf072N3QbC3RSvA1Zk4E2sv5oPl9hcAZBCNKyU_nWO1YL162KXjGu_jxK6eZDa1r8Gs_BvCJeZ9bgK5-FSQPHN/s72-c/1-grafana-spring-dashboard.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-2154446863066490147</id><published>2024-03-24T09:27:00.007+08:00</published><updated>2024-04-27T20:04:33.068+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="trading"/><title type='text'>How to Transfer Money from Coins PH to Binance</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Introduction&lt;/h2&gt;Worried about the risk of falling victim to scams while engaging in peer-to-peer (P2P) transactions for buying and selling coins? While there are several strategies to mitigate this risk, it&#39;s important to note that transfer fees can often be costly. One platform I&#39;ve found to have reasonable transfer fees is ARBITRUM. For this exercise, let&#39;s consider transferring a USDT coin from Coins PH to Binance.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For this exercise, you need a Binance and Coins PH accounts.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Preferring for Transfer - Binance&lt;/h2&gt;&lt;div&gt;We need to get the USDT wallet address in preparation for the transfer.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2.1 From the menu, select Spot.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDhlY7f9e_SIDbpq2Zi_2r3fCVpGnUPPFOZsYnxrBqvH9QJf9IJ7U0N1uHjEnPBRMpuMJkrzPNxFOxsosiH33fSL4w0y0ZvK9d8GK5qAfBf3smhEmhW_iLJ0Up6Q4BnObJbxqp93vopYb_foyamcQrBsoNZufX_B9H7cHSP5kpL5PGvgSjC3FFrL3y20Nd/s821/1-binance-spot.png&quot; style=&quot;clear: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;547&quot; data-original-width=&quot;821&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDhlY7f9e_SIDbpq2Zi_2r3fCVpGnUPPFOZsYnxrBqvH9QJf9IJ7U0N1uHjEnPBRMpuMJkrzPNxFOxsosiH33fSL4w0y0ZvK9d8GK5qAfBf3smhEmhW_iLJ0Up6Q4BnObJbxqp93vopYb_foyamcQrBsoNZufX_B9H7cHSP5kpL5PGvgSjC3FFrL3y20Nd/s16000/1-binance-spot.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2.2 Find the USDT coin, click the &quot;...&quot; menu, and select &quot;Deposit.&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXByWSreNHey4FqFazCFdpKElXLcEsbsGv6IiPOLD30dPLlF1QP_sK-HCteJu_UsJ24yam6z-NoaOtKDtEepPGctozF5PtcnMna9nd3eViu3HsOCTMv0Da4pNXo0yL-VyfdC7YmumVuhyphenhyphen4q877JhslvaPO4kQewremGBxLTTMcx0Di6YdHYZBjeTg6BrgC/s2052/2-USDT-wallet.png&quot; style=&quot;clear: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;692&quot; data-original-width=&quot;2052&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXByWSreNHey4FqFazCFdpKElXLcEsbsGv6IiPOLD30dPLlF1QP_sK-HCteJu_UsJ24yam6z-NoaOtKDtEepPGctozF5PtcnMna9nd3eViu3HsOCTMv0Da4pNXo0yL-VyfdC7YmumVuhyphenhyphen4q877JhslvaPO4kQewremGBxLTTMcx0Di6YdHYZBjeTg6BrgC/s16000/2-USDT-wallet.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2.3 Select the ARBITRUM network and copy the deposit address.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiYP4tUaFA2T2vnExg5tTvMMqFZljt_JRN6p16XPVUtEAKMW39BJUFMw4stxzInOjf_e1JvIpX-dm7mHqq87rSge95JlPrquf8523OVi7FXHY9JFWAHrsjiEQK0D_9NIPkH3PN1RF8-slDqqWdCuDrhO4f_C4CI4EPaYpsAHfNqelQfbFgh2dX6p3QhJPI/s1263/3-Binance-export-wallet-address.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1263&quot; data-original-width=&quot;1177&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiYP4tUaFA2T2vnExg5tTvMMqFZljt_JRN6p16XPVUtEAKMW39BJUFMw4stxzInOjf_e1JvIpX-dm7mHqq87rSge95JlPrquf8523OVi7FXHY9JFWAHrsjiEQK0D_9NIPkH3PN1RF8-slDqqWdCuDrhO4f_C4CI4EPaYpsAHfNqelQfbFgh2dX6p3QhJPI/s16000/3-Binance-export-wallet-address.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Making the Transfer in Coins PH&lt;/h2&gt;&lt;div&gt;3.1 Go to your account portfolio. Find USDT and select Send USDT.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9dxoWwgxCbWGuHn2sM-myZyooC5Ejs5WFqkuv6rDyrqaCVgIncEmjW1nr-blj1oaejWgHxQ1H2b5fCC3GtMKHoicJPDB35OGs-5FK0VNFknuNINvnerftMZLnO4V1Sj4r4IN19at-2m9u9C573QvPAEbqxUGFZJbAuSba5aINzm-ahGGkez3NzMPXKwTP/s1617/4-CoinsPH-portfolio.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;974&quot; data-original-width=&quot;1617&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9dxoWwgxCbWGuHn2sM-myZyooC5Ejs5WFqkuv6rDyrqaCVgIncEmjW1nr-blj1oaejWgHxQ1H2b5fCC3GtMKHoicJPDB35OGs-5FK0VNFknuNINvnerftMZLnO4V1Sj4r4IN19at-2m9u9C573QvPAEbqxUGFZJbAuSba5aINzm-ahGGkez3NzMPXKwTP/s16000/4-CoinsPH-portfolio.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;3.2 Select the Arbitrum network to match what we selected from Binance.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMdvSeDY7VzqCiQMLYBWtddbpW7QX0DFmIpZiqOU5Mc2su9jRzct1CWj8_105zfrxJkohBlIBI09snAs562vrmDnFtDYyjDlmm3qU3iK1YQIJoouk9QzHW5pW76cbiuay1kAwqzZT6Q_4-wJ5L3rf9vYOeDtQHumPviV7yDBbxMPLdXpL89FavJbdF90Ek/s1058/5-CoinsPH-arbitrum-network.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1058&quot; data-original-width=&quot;1042&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMdvSeDY7VzqCiQMLYBWtddbpW7QX0DFmIpZiqOU5Mc2su9jRzct1CWj8_105zfrxJkohBlIBI09snAs562vrmDnFtDYyjDlmm3qU3iK1YQIJoouk9QzHW5pW76cbiuay1kAwqzZT6Q_4-wJ5L3rf9vYOeDtQHumPviV7yDBbxMPLdXpL89FavJbdF90Ek/s16000/5-CoinsPH-arbitrum-network.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3.3 Specify the amount that you want to send.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6O0LA7ZcFLLwGqa3nBEgP6bZUoq6zlgLGcp5m3apmXSAZ7jOcm9aewHRq_BDoIWKkTlsXfd9kF30-fwJ1M1QMd2v6rqyeZ5BKwn-wmB77EeqZshoxXi5c_-DWOn1drssbwI7PuclCZFUMlQIyV8g_zDzwEehkLJeRhk2L-eknV31EhLtxmcquNakov2ur/s1858/6-CoinsPH-send.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1858&quot; data-original-width=&quot;1048&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6O0LA7ZcFLLwGqa3nBEgP6bZUoq6zlgLGcp5m3apmXSAZ7jOcm9aewHRq_BDoIWKkTlsXfd9kF30-fwJ1M1QMd2v6rqyeZ5BKwn-wmB77EeqZshoxXi5c_-DWOn1drssbwI7PuclCZFUMlQIyV8g_zDzwEehkLJeRhk2L-eknV31EhLtxmcquNakov2ur/s16000/6-CoinsPH-send.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;3.4 You will be presented with a list of checklists, once verified press &quot;I Understand&quot;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3.5 In the next screen, you will be asked to confirm your transaction details.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbEV0xhtUuM5mgPTYK1EqgjVUEcib6c5N76vO5MQF9UG8a5oC6QlzAOYRvNON8SZAZDEOLtJchJU5Jqtc2GeYOnpiThgLWDDs8ThujNrkVyyF7H7VvR3BNbBHFv5jO20TUQqDO0__eVmE_LYVKepWJReIFOCOreWwMdEqMvancmP5aMZychqGq_1aC7xYd/s1462/7-CoinsPH-confirm-tx.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1462&quot; data-original-width=&quot;1028&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbEV0xhtUuM5mgPTYK1EqgjVUEcib6c5N76vO5MQF9UG8a5oC6QlzAOYRvNON8SZAZDEOLtJchJU5Jqtc2GeYOnpiThgLWDDs8ThujNrkVyyF7H7VvR3BNbBHFv5jO20TUQqDO0__eVmE_LYVKepWJReIFOCOreWwMdEqMvancmP5aMZychqGq_1aC7xYd/s16000/7-CoinsPH-confirm-tx.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;3.6 The next step would be for you to validate the transaction by entering the security code sent via email.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3.7 The transaction will be processed, and you should receive a successful message after a minute or two.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHAF7EG8t2pSrc6fikU8bCS4ncx47-WP-hg9k7Th_C6xKivtVAOok5RnO2MB3u59dlzWUWIClj0btLFAdtRQD-t23Hp4P4Oy-EgY0DrcaaL7ZI01JFdKV41DV0Yi-RX0EmVuBPDkOWbE58KNGgzLpKSHGAOrvgo4PUc_HrSJGc1dL_xn6tzw6OK5GPcZmf/s2062/8-CoinsPH-transfer-status.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1250&quot; data-original-width=&quot;2062&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHAF7EG8t2pSrc6fikU8bCS4ncx47-WP-hg9k7Th_C6xKivtVAOok5RnO2MB3u59dlzWUWIClj0btLFAdtRQD-t23Hp4P4Oy-EgY0DrcaaL7ZI01JFdKV41DV0Yi-RX0EmVuBPDkOWbE58KNGgzLpKSHGAOrvgo4PUc_HrSJGc1dL_xn6tzw6OK5GPcZmf/s16000/8-CoinsPH-transfer-status.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;This process works the opposite way (Binance -&amp;gt; Coins PH).&lt;/b&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Disclaimer&lt;/h2&gt;&lt;div&gt;The transaction fee is subject to change without prior notice. Therefore, verifying the current rate beforehand and conducting a test transfer before proceeding with a large amount is essential.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/2154446863066490147/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/2154446863066490147?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2154446863066490147'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2154446863066490147'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/03/trading-send-coins-from-coinsph-to-binance.html' title='How to Transfer Money from Coins PH to Binance'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDhlY7f9e_SIDbpq2Zi_2r3fCVpGnUPPFOZsYnxrBqvH9QJf9IJ7U0N1uHjEnPBRMpuMJkrzPNxFOxsosiH33fSL4w0y0ZvK9d8GK5qAfBf3smhEmhW_iLJ0Up6Q4BnObJbxqp93vopYb_foyamcQrBsoNZufX_B9H7cHSP5kpL5PGvgSjC3FFrL3y20Nd/s72-c/1-binance-spot.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-3364231899477370593</id><published>2024-03-07T08:44:00.005+08:00</published><updated>2024-03-07T12:44:04.314+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="machine learning"/><title type='text'>Hands-On Coding: Exploring Hyperparameters for Programmers</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;Introduction&lt;/h2&gt;
&lt;div&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;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;In this blog you will learn:&lt;/div&gt;
&lt;div&gt;
  &lt;ol style=&quot;text-align: left;&quot;&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;/ol&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt; RandomizedSearchCV &lt;/h2&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;param_grid = {
    &quot;gamma&quot;: [0, 0.1, 0.2, 0.5, 1, 1.5, 2, 3, 6, 12, 20],
    &quot;learning_rate&quot;: [0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8],
    &quot;max_depth&quot;: [1, 2, 3, 4, 5, 6, 8, 12],
    &quot;n_estimators&quot;: [25, 50, 65, 80, 100, 115, 200]
}

grid_search = RandomizedSearchCV(estimator=classifier_0, param_distributions=param_grid, scoring=scoring)
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt; GridSearchCV &lt;/h2&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;param_grid = {
    &quot;gamma&quot;: [0, 0.1, 0.2, 0.5, 1, 1.5, 2, 3, 6, 12, 20],
    &quot;learning_rate&quot;: [0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8],
    &quot;max_depth&quot;: [2, 3, 4, 5, 6, 8, 12],
    &quot;n_estimators&quot;: [25, 50, 65, 80, 100, 115, 200]
}

grid_search = GridSearchCV(estimator=classifier_0, param_grid=param_grid, scoring=scoring)
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt; BayesSearchCV &lt;/h2&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;param_bayes = {
    &#39;gamma&#39;: Categorical(param_grid[&#39;gamma&#39;]),
    &#39;learning_rate&#39;: Categorical(param_grid[&#39;learning_rate&#39;]),
    &#39;max_depth&#39;: Categorical(param_grid[&#39;max_depth&#39;]),
    &#39;n_estimators&#39;: Categorical(param_grid[&#39;n_estimators&#39;])
}

grid_search = BayesSearchCV(estimator=classifier_0, search_spaces=param_bayes, scoring=scoring, n_jobs=-1, cv=10)
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt; Finding the Best HyperParameters &lt;/h2&gt;
&lt;pre class=&quot;brush: java&quot;&gt;best_model = grid_search.fit(X_train, y_train)
hyperparams = best_model.best_params_
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt; Building and Scoring the Classifier using the HyperParameters &lt;/h2&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;# Fitting the Model
ne = hyperparams[&#39;n_estimators&#39;]
lr = hyperparams[&#39;learning_rate&#39;]
md = hyperparams[&#39;max_depth&#39;]
gm = hyperparams[&#39;gamma&#39;]
print(&quot;Recommended Params &amp;gt;&amp;gt;&quot;, f&quot;ne: {ne},&quot;, f&quot;lr: {lr}&quot;, f&quot;md: {md}&quot;, f&quot;gm: {gm}&quot;)

# Build Classification Model
classifier_1 = XGBClassifier(
    base_score=0.5,
    colsample_bylevel=1,
    colsample_bynode=1,
    objective=objective,
    booster=&quot;gbtree&quot;,
    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(&quot;Training Preds: \n&quot;, 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(&quot;Average Accuracy K-Fold: &quot;, round(train_results.mean(), 2))
print(&quot;Std Deviation K-Fold: &quot;, round(train_results.std(), 2))
print(&quot;Precision Score 0: &quot;, round(precision_score(y_train, train_yhat, average=None)[0], 3))
print(&quot;Precision Score 1: &quot;, round(precision_score(y_train, train_yhat, average=None)[1], 3))
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt; Performance&amp;nbsp;&lt;/h2&gt;
&lt;div&gt;Machine: Laptop&amp;nbsp;&lt;/div&gt;
&lt;div&gt;Processor: AMD Ryzen 7&amp;nbsp;&lt;/div&gt;
&lt;div&gt;OS: Windows&amp;nbsp;&lt;/div&gt;
&lt;div&gt;DataFrame Shape: (7282, 17)&lt;/div&gt;
&lt;br /&gt;
&lt;table style=&#39;box-shadow: 0 0 20px rgba(0, 0, 0, 0.15)&#39;&gt;
  &lt;tbody&gt;
    &lt;tr style=&#39;background-color: #3d98d5; padding: 10px; color: white;&#39;&gt;
      &lt;th&gt;Technique&lt;/th&gt;
      &lt;th&gt;Time (s)&lt;/th&gt;
      &lt;th&gt;Avg Accuracy K-Fold&lt;/th&gt;
      &lt;th&gt;Std Deviation K-Fold&lt;/th&gt;
      &lt;th&gt;Precision Score: 0&lt;/th&gt;
      &lt;th&gt;Precision Score: 1&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RandomizedSearchCV &lt;/td&gt;
      &lt;td&gt;17.02&lt;/td&gt;
      &lt;td&gt;0.54&lt;/td&gt;
      &lt;td&gt;0.07&lt;/td&gt;
      &lt;td&gt;0.576&lt;/td&gt;
      &lt;td&gt;0.601&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BayesSearchCV &lt;/td&gt;
      &lt;td&gt;105.39&lt;/td&gt;
      &lt;td&gt;0.52&lt;/td&gt;
      &lt;td&gt;0.05&lt;/td&gt;
      &lt;td&gt;0.589&lt;/td&gt;
      &lt;td&gt;0.568&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GridSearchCV &lt;/td&gt;
      &lt;td&gt;9413.60&lt;/td&gt;
      &lt;td&gt;0.53&lt;/td&gt;
      &lt;td&gt;0.06&lt;/td&gt;
      &lt;td&gt;0.605&lt;/td&gt;
      &lt;td&gt;0.623&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/3364231899477370593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/3364231899477370593?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/3364231899477370593'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/3364231899477370593'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/03/machine-learning-exploring-hyperparameters.html' title='Hands-On Coding: Exploring Hyperparameters for Programmers'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-4823589556627363991</id><published>2024-03-02T11:29:00.002+08:00</published><updated>2024-03-02T11:29:19.053+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring-rest"/><title type='text'>Understanding How Scope Affects Values in Your Spring REST Controller</title><content type='html'>&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;/p&gt;

&lt;pre class=&quot;brush: java&quot;&gt;
@RestController
@Scope([SCOPE_VALUE])
public class XXXScopeController {}
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgk_oXEhZyT_QlSTw19VXRUb4GY9GHmLi7-b44tgQTmVNtM03xjfZR34u2BH6r-UXG5rHgrV-LuiUawLP-TZFKwfeo0_QYuOdsfHAoIW7_Kdl5LmGYNTgZQBfBmLK3TFYOU59PfxBZJkbIgrpoBrzigLvcPZiRZ0S9JjjKDUCjLMerIIn0HZDYOjZvBFOGM/s2958/spring-scopes.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;707&quot; data-original-width=&quot;2958&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgk_oXEhZyT_QlSTw19VXRUb4GY9GHmLi7-b44tgQTmVNtM03xjfZR34u2BH6r-UXG5rHgrV-LuiUawLP-TZFKwfeo0_QYuOdsfHAoIW7_Kdl5LmGYNTgZQBfBmLK3TFYOU59PfxBZJkbIgrpoBrzigLvcPZiRZ0S9JjjKDUCjLMerIIn0HZDYOjZvBFOGM/s16000/spring-scopes.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/4823589556627363991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/4823589556627363991?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/4823589556627363991'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/4823589556627363991'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/03/spring-rest-scope-annotation.html' title='Understanding How Scope Affects Values in Your Spring REST Controller'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgk_oXEhZyT_QlSTw19VXRUb4GY9GHmLi7-b44tgQTmVNtM03xjfZR34u2BH6r-UXG5rHgrV-LuiUawLP-TZFKwfeo0_QYuOdsfHAoIW7_Kdl5LmGYNTgZQBfBmLK3TFYOU59PfxBZJkbIgrpoBrzigLvcPZiRZ0S9JjjKDUCjLMerIIn0HZDYOjZvBFOGM/s72-c/spring-scopes.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-3256542209973570069</id><published>2024-02-24T17:05:00.002+08:00</published><updated>2024-02-24T17:05:55.383+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="dataframe"/><category scheme="http://www.blogger.com/atom/ns#" term="machine learning"/><title type='text'>How to Convert Vertically Stored Asset Data into Columnar Format for Cointegration Analysis</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;Introduction&lt;/h2&gt;&lt;div&gt;This piece of code fetches asset information from a table stored vertically.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1VhFt-PwfCCW0UK4eM6EkhpJt5Y3JcSGX1Hlw94wWDch7-3g2BBbmrtHKI-FGAFYtBRMPI7b5tqoT36rJbXwQCLlKSnUcsbXiMtn8BRHUXFvXCouNqUf7GNofZZPuaHGklGnJ0SOVjTLnjEAIj8rvK2_vLM_oolHm0CTGWvpp4BpFMTjv7iPNpdRD3dYf/s1991/pse_stocksquotes.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;886&quot; data-original-width=&quot;1991&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1VhFt-PwfCCW0UK4eM6EkhpJt5Y3JcSGX1Hlw94wWDch7-3g2BBbmrtHKI-FGAFYtBRMPI7b5tqoT36rJbXwQCLlKSnUcsbXiMtn8BRHUXFvXCouNqUf7GNofZZPuaHGklGnJ0SOVjTLnjEAIj8rvK2_vLM_oolHm0CTGWvpp4BpFMTjv7iPNpdRD3dYf/s16000/pse_stocksquotes.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Dependencies&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Install the following package.&lt;/h3&gt;
&lt;pre class=&quot;brush: java&quot;&gt;conda install pandas
conda install numpy as np
conda install mysql-connector-python
conda install sqlalchemy
conda install pymysql
&lt;/pre&gt;
&lt;p&gt;Hands-on Coding&lt;/p&gt;&lt;p&gt;Connect to the database&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;def query_df(query):
    try: 
        engine_uri = f&quot;mysql+pymysql://db_user:db_pass_123@localhost:3306/tradewise_pse&quot;
        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;/pre&gt;
&lt;p&gt;
  Fetching the Dataset&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;if not load_existing:
    sql_distinct_tickers = &quot;select ticker from candlestick where event_time=&#39;2023-12-29&#39; and ticker not like &#39;^%%&#39;&quot;
    df_tickers = query_df(sql_distinct_tickers)
    
    df = pd.DataFrame(index=[&#39;event_time&#39;])
    
    ### Get the candlesticks
    for ticker in df_tickers[&#39;ticker&#39;]:
        sql_ticker_col = &quot;select event_time, close from candlestick where ticker=&#39;{0}&#39;&quot;
        df_temp = query_df(sql_ticker_col.format(ticker))
        df_temp.set_index(&#39;event_time&#39;, inplace=True)
        df_temp.rename(columns={&#39;close&#39;: ticker}, inplace=True)        
        df = df.add(df_temp, fill_value=0)
        
    df.to_csv(file_name)
&lt;/pre&gt;
&lt;p&gt;Load the dataset from file&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;df = pd.read_csv(file_name, index_col=0)
df.drop(index=df.index[-1],axis=0, inplace=True)
&lt;/pre&gt;
&lt;p&gt;Drop NA&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;df.dropna(axis=1, inplace=True)
&lt;/pre&gt;
&lt;p&gt;Print the dataset&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;print(f&quot;Shape: {df.shape}&quot;)
print(f&quot;Null values: {df.isnull().values.any()}&quot;)
df
&lt;/pre&gt;
&lt;p&gt;Save to a file&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;df.to_csv(file_name)
&lt;/pre&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSfeyHtZICnuPLvmU3c2gffwvghMXBtdEEGyAX59_x-avffROB52Oa_XUJg9eohQb91AFkCL_SNU4YooGzxz2rgYQpGL97PNVRrgjimOT0rSAHufdVyEWA_bXA_wjfmRSB7jUNnfkHOkm0wgYDmBwc3JE0oMF-wqnHdnCgOGZBUA4jsggYtoFKY7EQ4nQb/s2349/columnar_assets.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;510&quot; data-original-width=&quot;2349&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSfeyHtZICnuPLvmU3c2gffwvghMXBtdEEGyAX59_x-avffROB52Oa_XUJg9eohQb91AFkCL_SNU4YooGzxz2rgYQpGL97PNVRrgjimOT0rSAHufdVyEWA_bXA_wjfmRSB7jUNnfkHOkm0wgYDmBwc3JE0oMF-wqnHdnCgOGZBUA4jsggYtoFKY7EQ4nQb/s16000/columnar_assets.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class=&quot;brush: java&quot;&gt;This procedure is in preparation for cointegration testing.&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/3256542209973570069/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/3256542209973570069?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/3256542209973570069'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/3256542209973570069'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/02/machine-learning-columnar-asset-record-for-cointegration.html' title='How to Convert Vertically Stored Asset Data into Columnar Format for Cointegration Analysis'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1VhFt-PwfCCW0UK4eM6EkhpJt5Y3JcSGX1Hlw94wWDch7-3g2BBbmrtHKI-FGAFYtBRMPI7b5tqoT36rJbXwQCLlKSnUcsbXiMtn8BRHUXFvXCouNqUf7GNofZZPuaHGklGnJ0SOVjTLnjEAIj8rvK2_vLM_oolHm0CTGWvpp4BpFMTjv7iPNpdRD3dYf/s72-c/pse_stocksquotes.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-2103492419664320035</id><published>2024-02-24T16:38:00.002+08:00</published><updated>2024-02-24T16:39:17.394+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="machine learning"/><title type='text'>Learn to Incorporate Rolling Hurst Values into Your DataFrame</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;The hurst function.&lt;/h2&gt;&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&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;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;
Adding to our DataFrame&lt;/h2&gt;&lt;div&gt;The hurst value is computed with the last 14 close values.&lt;/div&gt;&lt;pre class=&quot;brush: java&quot;&gt;df[&#39;Hurst&#39;] = df[&#39;close&#39;].rolling(14).apply(hurst, raw=True)
df[10:20]

&lt;/pre&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoXLAvUM3MLIXGbFp8daR2v4FJcRbWDPHqYHtEc8YsFM_TDF4Cskty-kgG6LzmU3BgNkBTZOfoIGsGR4DQ3-1UeD4Y8oD39oipEd3e2ioH7HJRNPaP4GlO6wwJU0AgabUJfUy9W4Lqyf91n2xO0L5QLreudVx5o2jwfbTrGEu1SYRiI2FRzMIeFMxXkR-B/s1577/adding_rolling_hurst_values.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1027&quot; data-original-width=&quot;1577&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoXLAvUM3MLIXGbFp8daR2v4FJcRbWDPHqYHtEc8YsFM_TDF4Cskty-kgG6LzmU3BgNkBTZOfoIGsGR4DQ3-1UeD4Y8oD39oipEd3e2ioH7HJRNPaP4GlO6wwJU0AgabUJfUy9W4Lqyf91n2xO0L5QLreudVx5o2jwfbTrGEu1SYRiI2FRzMIeFMxXkR-B/s16000/adding_rolling_hurst_values.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/2103492419664320035/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/2103492419664320035?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2103492419664320035'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2103492419664320035'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/02/machine-learning-rolling-hurst-value-in-dataframe.html' title='Learn to Incorporate Rolling Hurst Values into Your DataFrame'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoXLAvUM3MLIXGbFp8daR2v4FJcRbWDPHqYHtEc8YsFM_TDF4Cskty-kgG6LzmU3BgNkBTZOfoIGsGR4DQ3-1UeD4Y8oD39oipEd3e2ioH7HJRNPaP4GlO6wwJU0AgabUJfUy9W4Lqyf91n2xO0L5QLreudVx5o2jwfbTrGEu1SYRiI2FRzMIeFMxXkR-B/s72-c/adding_rolling_hurst_values.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-6861401765691658964</id><published>2024-02-01T15:48:00.006+08:00</published><updated>2024-02-02T08:53:07.321+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="glowroot"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><title type='text'>Implementing Glowroot: A Hands-On Tech Review for Application Monitoring Mastery</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Introduction&lt;/h2&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;b&gt;Glowroot&lt;/b&gt;&amp;nbsp;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;h2 style=&quot;text-align: left;&quot;&gt;2. Key Features&lt;/h2&gt;
&lt;div&gt;
  &lt;ul style=&quot;text-align: left;&quot;&gt;
    &lt;li&gt;
      &lt;b&gt;Response Time Breakdown Charts:&lt;/b&gt; Visualizes the breakdown of response times for better analysis.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Response Time Percentile Charts:&lt;/b&gt; Offers percentile charts to understand response time distribution.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;SQL Capture and Aggregation:&lt;/b&gt; Captures and aggregates SQL queries for in-depth analysis.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Service Call Capture and Aggregation:&lt;/b&gt; Gathers and aggregates data on service calls for comprehensive insights.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;MBean Attribute Capture and Charts:&lt;/b&gt; Monitors and charts MBean attributes for performance evaluation.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Configurable Alerting:&lt;/b&gt; Allows users to configure alerts based on specific criteria.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Historical Rollup:&lt;/b&gt; Provides historical data rollup at different intervals (1m, 5m, 30m, 4h) with configurable retention settings.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Full Support for Async Requests:&lt;/b&gt; Supports asynchronous requests that span multiple threads.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Responsive UI with Mobile Support:&lt;/b&gt; User-friendly and responsive interface with mobile support for accessibility.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Optional Central Collector:&lt;/b&gt; Offers the flexibility of an optional central collector for centralized data management.
    &lt;/li&gt;
    &lt;li&gt;
      &lt;b&gt;Supports Multiple Application Servers:&lt;/b&gt;&amp;nbsp;Wildfly, JBoss EAP, Tomcat, TomEE, Jetty, Glassfish, Payara, WebLogic, WebSphere
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 style=&quot;text-align: left;&quot;&gt;3. Central Collector&lt;/h2&gt;
  &lt;div&gt;The central collector collects runtime information from the registered services and offers a GUI for easy viewing. &lt;a href=&quot;https://github.com/glowroot/glowroot/wiki/Central-Collector-Installation&quot;&gt;https://github.com/glowroot/glowroot/wiki/Central-Collector-Installation&lt;/a&gt;
  &lt;/div&gt;
  &lt;h3 style=&quot;text-align: left;&quot;&gt;3.1 Installation&lt;/h3&gt;
  &lt;div&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&#39;ll share how it can be run locally.&lt;/div&gt;
  &lt;div&gt;Here&#39;s my docker-compose file for running glowroot-central with cassandra. Override username, password, and contactPoints in glowroot-central.properties.&lt;/div&gt;
  &lt;pre class=&quot;brush: java&quot;&gt;
  version: &#39;3.8&#39;

networks:
  tradewise-network:

services:
  cassandra:
    image: cassandra:latest
    container_name: cassandra
    restart: unless-stopped
    ports:
      - &quot;9042:9042&quot;
    networks:
      - tradewise-network

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

  &lt;/pre&gt;
  &lt;h2 style=&quot;text-align: left;&quot;&gt;4. Instrumentation&lt;/h2&gt;
&lt;/div&gt;
&lt;div&gt;There are 4 ways in which we can integrate GlowRoot into our services&amp;nbsp; &lt;a href=&quot;https://glowroot.org/instrumentation.html&quot;&gt;https://glowroot.org/instrumentation.html&lt;/a&gt;. For this exercise, we will focus on using the agent API. &lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.1 Editing the pom.xml File&lt;/h3&gt;
&lt;div&gt;In our Spring Boot project&#39;s pom.xml file, add the GlowRoot agent configuration.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
&amp;lt;plugin&gt;
	&amp;lt;groupId&gt;org.apache.maven.plugins&amp;lt;/groupId&gt;
	&amp;lt;artifactId&gt;maven-resources-plugin&amp;lt;/artifactId&gt;
	&amp;lt;executions&gt;
		&amp;lt;execution&gt;
			&amp;lt;id&gt;glowroot-plugins&amp;lt;/id&gt;
			&amp;lt;phase&gt;validate&amp;lt;/phase&gt;
			&amp;lt;goals&gt;
				&amp;lt;goal&gt;copy-resources&amp;lt;/goal&gt;
			&amp;lt;/goals&gt;
			&amp;lt;configuration&gt;
				&amp;lt;outputDirectory&gt;target/glowroot-plugins/&amp;lt;/outputDirectory&gt;
				&amp;lt;resources&gt;
					&amp;lt;resource&gt;
						&amp;lt;directory&gt;glowroot-plugins&amp;lt;/directory&gt;
						&amp;lt;filtering&gt;false&amp;lt;/filtering&gt;
					&amp;lt;/resource&gt;
				&amp;lt;/resources&gt;
			&amp;lt;/configuration&gt;
		&amp;lt;/execution&gt;
	&amp;lt;/executions&gt;
&amp;lt;/plugin&gt;

&amp;lt;plugin&amp;gt;
	&amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;maven-dependency-plugin&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;${maven-dependency-plugin.version}&amp;lt;/version&amp;gt;
	&amp;lt;executions&amp;gt;
		&amp;lt;execution&amp;gt;
			&amp;lt;id&amp;gt;copy-glowroot-jar&amp;lt;/id&amp;gt;
			&amp;lt;phase&amp;gt;prepare-package&amp;lt;/phase&amp;gt;
			&amp;lt;goals&amp;gt;
				&amp;lt;goal&amp;gt;copy&amp;lt;/goal&amp;gt;
			&amp;lt;/goals&amp;gt;
		&amp;lt;/execution&amp;gt;
	&amp;lt;/executions&amp;gt;
	&amp;lt;configuration&amp;gt;
		&amp;lt;artifactItems&amp;gt;
			&amp;lt;artifactItem&amp;gt;
				&amp;lt;groupId&amp;gt;org.glowroot&amp;lt;/groupId&amp;gt;
				&amp;lt;artifactId&amp;gt;glowroot-agent&amp;lt;/artifactId&amp;gt;
				&amp;lt;version&amp;gt;${glowroot-agent.version}&amp;lt;/version&amp;gt;
				&amp;lt;type&amp;gt;jar&amp;lt;/type&amp;gt;
				&amp;lt;overWrite&amp;gt;false&amp;lt;/overWrite&amp;gt;
				&amp;lt;outputDirectory&amp;gt;${project.build.directory}&amp;lt;/outputDirectory&amp;gt;
				&amp;lt;destFileName&amp;gt;glowroot.jar&amp;lt;/destFileName&amp;gt;
			&amp;lt;/artifactItem&amp;gt;
		&amp;lt;/artifactItems&amp;gt;
	&amp;lt;/configuration&amp;gt;
&amp;lt;/plugin&amp;gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.2 Preparing the Dockerfile&lt;/h3&gt;&lt;div&gt;We need to add the GlowRoot files in the docker image.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;
# Base Image
FROM eclipse-temurin:17-jdk-alpine
LABEL author=CzetsuyaTech
LABEL maintainer=CzetsuyaTech

# Configuration
WORKDIR /

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

USER czetsuyatech
ADD --chown=czetsuyatech:czetsuyatech target/glowroot.jar /glowroot
ADD --chown=czetsuyatech:czetsuyatech target/glowroot-plugins /glowroot/plugins

# Service
ADD --chown=czetsuyatech:czetsuyatech target/*.jar app.jar

# Start
ENV JAVA_JAR &quot;/app.jar&quot;
ENV JAVA_OTHERS &quot;-Xshare:off&quot;
ENV JAVA_OPTS ${JAVA_OPTS}
ENV JAVA_MEM ${JAVA_MEM}

RUN echo &quot;exec java $JAVA_MEM $JAVA_OPTS $JAVA_OTHERS -jar $JAVA_JAR&quot;
ENTRYPOINT exec java $JAVA_MEM $JAVA_OPTS $JAVA_OTHERS -jar $JAVA_JAR

&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.3 Running the Spring Boot Service&lt;/h3&gt;
&lt;div&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;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
JAVA_OPTS=-javaagent:glowroot/glowroot.jar -Dglowroot.collector.address=localhost:8181 -server -Dglowroot.agent.id=TradewiseAI
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;5. GUI&lt;/h2&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.1 Usage&lt;/h3&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaxjUvM36zJOd2-i8oDTvR8icamUjS6PHhoIpsfrXOMSxdipyxAORkTgmSGt033C8Ki9KwwFeGE1NbvR7Ns_SWS3HCpWZBBVscZRDALuqXXncEQRG3bdrQR4UktvwX0MHGKxuFejr3xixWePJCO88003vpAOgOAspkbyyT2Loa44o2_VxJND48gVoyI_yl/s3342/web_transactions.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1875&quot; data-original-width=&quot;3342&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaxjUvM36zJOd2-i8oDTvR8icamUjS6PHhoIpsfrXOMSxdipyxAORkTgmSGt033C8Ki9KwwFeGE1NbvR7Ns_SWS3HCpWZBBVscZRDALuqXXncEQRG3bdrQR4UktvwX0MHGKxuFejr3xixWePJCO88003vpAOgOAspkbyyT2Loa44o2_VxJND48gVoyI_yl/s16000/web_transactions.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h3&gt;5.2 Transactions&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;5.2.1 Web&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;div&gt;To assess our REST Endpoints&#39; performance, we access the &quot;Web&quot; section. In the provided example:&lt;/div&gt;
  &lt;div&gt;
    &lt;ol style=&quot;text-align: left;&quot;&gt;
      &lt;li&gt;By choosing &quot;Response Time,&quot; we can identify which HttpRequests and JDBC Queries are taking longer in the REST Endpoints.&lt;/li&gt;
      &lt;li&gt;Opting for &quot;Slow Traces&quot; allows us to pinpoint the specific endpoint that consumes more time.&lt;/li&gt;
      &lt;li&gt;Selecting &quot;Queries&quot; 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;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;5.2.2 Background&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;div&gt;To analyze our background performance, we navigate to the &quot;Background&quot; section. In the given example:&lt;/div&gt;
  &lt;div&gt;
    &lt;ol&gt;
      &lt;li&gt;Choosing &quot;Response Time&quot; 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 &quot;Slow Traces&quot; reveals that some calls take more time, providing insights into areas that may require attention.&lt;/li&gt;
      &lt;li&gt;Selecting &quot;Queries&quot; 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;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
      &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi76KdeL-qHs036T9G7jP3Fdv5J1sPjQldEfdejFhXqTu2vG3Z4VqY8N694VYGhQTpAmyzBvPHc0FU0gsuHSzYjF9KMKtezpxOD5MmtCgaX6ePBc3rFWEHs0d0xgc8zwN-vwPyCAyuRybqJ8ZIA6otXyCtti21vzv_nfPATdRwid5vrRZFOf2pQKLmHTsEB/s2994/glowroot-background.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
        &lt;img border=&quot;0&quot; data-original-height=&quot;1723&quot; data-original-width=&quot;2994&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi76KdeL-qHs036T9G7jP3Fdv5J1sPjQldEfdejFhXqTu2vG3Z4VqY8N694VYGhQTpAmyzBvPHc0FU0gsuHSzYjF9KMKtezpxOD5MmtCgaX6ePBc3rFWEHs0d0xgc8zwN-vwPyCAyuRybqJ8ZIA6otXyCtti21vzv_nfPATdRwid5vrRZFOf2pQKLmHTsEB/s16000/glowroot-background.png&quot; /&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;5.2.3 Startup&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;div&gt;To assess our startup performance, we can utilize the &quot;Startup&quot; section. In the provided example:&lt;/div&gt;
  &lt;div&gt;
    &lt;ol style=&quot;text-align: left;&quot;&gt;
      &lt;li&gt;By choosing &quot;Response Time,&quot; we can identify which startup and filter init processes consume more time.&lt;/li&gt;
      &lt;li&gt;Opting for &quot;Slow Traces&quot; reveals the duration it takes for the context to initialize fully.&lt;/li&gt;
      &lt;li&gt;Selecting &quot;Queries&quot; 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;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.3 Errors&lt;/h3&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;5.3.1 Web&lt;/h4&gt;
&lt;div&gt;
  &lt;div&gt;To visualize errors in our REST endpoints, we can navigate to the &quot;Web&quot; section. In the following example:&lt;/div&gt;
  &lt;div&gt;
    &lt;ul style=&quot;text-align: left;&quot;&gt;
      &lt;li&gt;Choosing &quot;Error Messages&quot; reveals errors of type XXXException.&lt;/li&gt;
      &lt;li&gt;Opting for &quot;Error Traces&quot; 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;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.4 JVM&lt;/h3&gt;
&lt;div&gt;In our services&#39; 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;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKL8sWMZ55vRtxhQC8m2rKPEqcf-UE8D2xbhDVMiB2JQBylCnly1qt-BTvpLOQHkEdH7IrF088oE1u_Arbwe6rOiUboqKPnmjRpcTLE-aLc4_wv6zhTIT0H1EjYMiYOdVkimBV-zx0Q_2FhT4oG552wsp6BuvIIHCkcXSzjW3wYBIpj_Kq5HzYrtGOSAdS/s3007/glowroot-jvm.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1853&quot; data-original-width=&quot;3007&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKL8sWMZ55vRtxhQC8m2rKPEqcf-UE8D2xbhDVMiB2JQBylCnly1qt-BTvpLOQHkEdH7IrF088oE1u_Arbwe6rOiUboqKPnmjRpcTLE-aLc4_wv6zhTIT0H1EjYMiYOdVkimBV-zx0Q_2FhT4oG552wsp6BuvIIHCkcXSzjW3wYBIpj_Kq5HzYrtGOSAdS/s16000/glowroot-jvm.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.5 MBeanTree&lt;/h3&gt;
&lt;div&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;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIM2GtuuJGpHAlqMaCg8p5XmqI_TEbTVDOHOA63Ab1IqssFGroEYEiiNYgr4oTOo2G4-8v9kjWbyjH0ep96tDlOYunIF1kQml6uR3wldnGObLfSyr-1Odt3p2e_R7ALPN0_LlHIxR9IbnrRJB8UWYcHGDzUKWgRwEhZWxY3ljGDKEk8rj-rIXkyCLhNQfD/s2239/glowroot-mbean.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1746&quot; data-original-width=&quot;2239&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIM2GtuuJGpHAlqMaCg8p5XmqI_TEbTVDOHOA63Ab1IqssFGroEYEiiNYgr4oTOo2G4-8v9kjWbyjH0ep96tDlOYunIF1kQml6uR3wldnGObLfSyr-1Odt3p2e_R7ALPN0_LlHIxR9IbnrRJB8UWYcHGDzUKWgRwEhZWxY3ljGDKEk8rj-rIXkyCLhNQfD/s16000/glowroot-mbean.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;6. Recommendation&lt;/h2&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&#39;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;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/6861401765691658964/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/6861401765691658964?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/6861401765691658964'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/6861401765691658964'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2024/01/tech-in-review-glowroot.html' title='Implementing Glowroot: A Hands-On Tech Review for Application Monitoring Mastery'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaxjUvM36zJOd2-i8oDTvR8icamUjS6PHhoIpsfrXOMSxdipyxAORkTgmSGt033C8Ki9KwwFeGE1NbvR7Ns_SWS3HCpWZBBVscZRDALuqXXncEQRG3bdrQR4UktvwX0MHGKxuFejr3xixWePJCO88003vpAOgOAspkbyyT2Loa44o2_VxJND48gVoyI_yl/s72-c/web_transactions.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-5407462918627604537</id><published>2023-12-14T08:00:00.003+08:00</published><updated>2023-12-14T08:00:18.024+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="software-engineering"/><title type='text'>Navigating the Code: A Guide to Environment Variables, Configuration, and Feature Flags</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQsgf1TsulQfEa1eVPYjyjr0V9zEWKvH2CH4B4MvGaQW1Rs7gp-gX1eMQB-pBw1xS-a9yK89tYXbHVsiwyvg-PVHQDYK5cmf1pCYwh5dPzpSCTE8JWp3FPXCvkYdbHlF-SbzQTN9utPp5F-kdfaW1Zzp4sJdARoXpqLEIx31PewNksrF27b7oz0jC8uipz/s1920/nihon-graphy-nCvi-gS5r88-unsplash.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1278&quot; data-original-width=&quot;1920&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQsgf1TsulQfEa1eVPYjyjr0V9zEWKvH2CH4B4MvGaQW1Rs7gp-gX1eMQB-pBw1xS-a9yK89tYXbHVsiwyvg-PVHQDYK5cmf1pCYwh5dPzpSCTE8JWp3FPXCvkYdbHlF-SbzQTN9utPp5F-kdfaW1Zzp4sJdARoXpqLEIx31PewNksrF27b7oz0jC8uipz/s16000/nihon-graphy-nCvi-gS5r88-unsplash.jpg&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Determining the optimal location for storing essential information crucial for an application&#39;s functionality necessitates thoughtful planning. This guide aims to assist you in making informed decisions about where to store crucial data. Here, the term &quot;application&quot; 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&#39;s crucial to evaluate the volatility of each pair—how frequently and by whom it might undergo changes. 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 style=&quot;text-align: left;&quot;&gt;Environment Variables&lt;/h2&gt;&lt;div&gt;&lt;div&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&#39;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;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Application Configuration&lt;/h2&gt;&lt;div&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&#39;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;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Runtime User Settings&lt;/h2&gt;&lt;div&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;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Here are three options for handling configuration updates:&lt;/h3&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Re-read Configuration All the Time:&lt;/b&gt;&amp;nbsp;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;/li&gt;&lt;li&gt;&lt;b&gt;Re-read Configuration at Set Intervals (e.g., Every 15 Minutes):&amp;nbsp;&lt;/b&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;/li&gt;&lt;li&gt;&lt;b&gt;Receive Push Notifications When New Application Configuration Is Available:&lt;/b&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;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/5407462918627604537/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/5407462918627604537?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5407462918627604537'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5407462918627604537'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/12/software-engineering-environment-variables.html' title='Navigating the Code: A Guide to Environment Variables, Configuration, and Feature Flags'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQsgf1TsulQfEa1eVPYjyjr0V9zEWKvH2CH4B4MvGaQW1Rs7gp-gX1eMQB-pBw1xS-a9yK89tYXbHVsiwyvg-PVHQDYK5cmf1pCYwh5dPzpSCTE8JWp3FPXCvkYdbHlF-SbzQTN9utPp5F-kdfaW1Zzp4sJdARoXpqLEIx31PewNksrF27b7oz0jC8uipz/s72-c/nihon-graphy-nCvi-gS5r88-unsplash.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-1321407832455683243</id><published>2023-12-11T18:18:00.007+08:00</published><updated>2023-12-13T19:16:08.621+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="software-engineering"/><title type='text'>Navigating Software and Systems Architecture: A Curated Collection of Resources</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDsV_TKFEGAHMLR4zHjOONgczYgCjy0XcnJCcaJbpa1cIArBfoHzm-ESEL496wVzk77smH1gnRUCNjtoPizgcEdLfoKgA048O9pXpG0e_mGyM_Q0fptd2AOLwzNfRwPaXqyW1nLRi4-e9PCeNNlIHLbKbfNQ-TTKdq48-Oh_rbwwAZgZfeQy5TIfs0GNgR/s1920/books.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1026&quot; data-original-width=&quot;1920&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDsV_TKFEGAHMLR4zHjOONgczYgCjy0XcnJCcaJbpa1cIArBfoHzm-ESEL496wVzk77smH1gnRUCNjtoPizgcEdLfoKgA048O9pXpG0e_mGyM_Q0fptd2AOLwzNfRwPaXqyW1nLRi4-e9PCeNNlIHLbKbfNQ-TTKdq48-Oh_rbwwAZgZfeQy5TIfs0GNgR/s16000/books.jpg&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;These books serve as valuable references for building software systems:&lt;/p&gt;&lt;li&gt;Martin Fowler: Patterns of Enterprise Application Architecture&lt;/li&gt;&lt;li&gt;Robert C. Martin: Clean Architecture: A Craftsman&#39;s Guide to Software Structure and Design&lt;/li&gt;&lt;li&gt;Martin Kleppmann: Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems&lt;/li&gt;&lt;li&gt;Len Bass, Paul Clements, Rick Kazman: Software Architecture in Practice&lt;/li&gt;&lt;li&gt;Gregor Hope: Enterprise Integration Patterns&lt;/li&gt;&lt;li&gt;Erich Gamma: Design Patterns&lt;/li&gt;&lt;li&gt;Chris Richardson: Microservice Patterns&lt;/li&gt;&lt;li&gt;Mark Richards: Fundamentals of Software Architecture&lt;/li&gt;&lt;li&gt;Mark Masse: REST API Design Rulebook: Designing Consistent RESTful Web Service Interfaces and here&lt;/li&gt;&lt;li&gt;Matthew Skelton: Team Topologies&lt;/li&gt;&lt;li&gt;Svyatoslav Kotusev: The Practice of Enterprise Architecture: A Modern Approach to Business and IT Alignment&lt;/li&gt;&lt;br /&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Websites&lt;/h1&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;https://microservices.io -&amp;nbsp;online version of Chris Richardsons&#39; book&lt;/li&gt;&lt;li&gt;https://www.enterpriseintegrationpatterns.com/patterns/messaging -&amp;nbsp;online version of Gregor Hopes book&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/1321407832455683243/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/1321407832455683243?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/1321407832455683243'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/1321407832455683243'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/12/software-architecture-curated-collection.html' title='Navigating Software and Systems Architecture: A Curated Collection of Resources'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDsV_TKFEGAHMLR4zHjOONgczYgCjy0XcnJCcaJbpa1cIArBfoHzm-ESEL496wVzk77smH1gnRUCNjtoPizgcEdLfoKgA048O9pXpG0e_mGyM_Q0fptd2AOLwzNfRwPaXqyW1nLRi4-e9PCeNNlIHLbKbfNQ-TTKdq48-Oh_rbwwAZgZfeQy5TIfs0GNgR/s72-c/books.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-3922280946788795805</id><published>2023-09-07T20:42:00.002+08:00</published><updated>2023-09-07T20:42:37.968+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-cloud"/><title type='text'>Personalized Spring Boot Startups: Custom Banners and Build Information</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Overview&lt;/h2&gt;&lt;p&gt;By default, when a Spring Boot application starts, it displays a banner. In this article, we will explore the process of creating a custom banner and utilizing it within Spring Boot applications.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Creating a Banner&lt;/h2&gt;&lt;p&gt;Before we begin, it&#39;s essential to generate the custom banner, which will appear during the application startup process. You can choose to create the custom banner from scratch or leverage various tools designed for this purpose.&lt;/p&gt;&lt;p&gt;In this example, we are generating CzetsuyaTech&#39;s logo from https://devops.datenkollektiv.de/banner.txt/index.html.&lt;/p&gt;&lt;p&gt;Create a new file &quot;banner.txt&quot; inside the resources folder and copy and paste this content. This will print a colored banner with application information.&lt;/p&gt;&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;pre style=&quot;font-family: &amp;quot;Fira Code&amp;quot;, monospace; font-size: 12pt;&quot;&gt;&lt;div&gt;&lt;pre style=&quot;font-family: &amp;quot;Fira Code&amp;quot;, monospace; font-size: 12pt;&quot;&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}  _____ ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}         _                          ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;} _______       ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}  _&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;} / ____|${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}        | |                         ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}|__   __|      ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;} | |&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}| |     ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;} _______| |_ ___ _   _ _   _  __ _  ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}   | |${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}  ___  ___| |__&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}| |     ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}|_  / _ \ __/ __| | | | | | |/ _` | ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}   | |${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;} / _ \/ __| &#39;_ \&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}| |____ ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;} / /  __/ |_\__ \ |_| | |_| | (_| | ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}   | |${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}|  __/ (__| | | |&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;} \_____/${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}/___\___|\__|___/\__,_|\__, |\__,_| ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}   |_|${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;} \___|\___|_| |_|&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}        ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}                        __/ |&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BLUE&lt;/span&gt;}        ${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.WHITE&lt;/span&gt;}                       |___/&lt;br /&gt;${&lt;span style=&quot;color: #cc7832;&quot;&gt;AnsiColor.BRIGHT_GREEN&lt;/span&gt;}#application.title# v#application.version#&lt;br /&gt;Build: #build.time#&lt;br /&gt;Powered by Spring Boot ${&lt;span style=&quot;color: #cc7832;&quot;&gt;spring-boot.version&lt;/span&gt;}&lt;/pre&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Replacing the Variable Information in the Banner.&lt;/h2&gt;&lt;div&gt;To replace the build information in the banner, we will use the replacer plugin.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;&amp;lt;plugin&amp;gt;
	&amp;lt;groupId&amp;gt;com.google.code.maven-replacer-plugin&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;replacer&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;1.5.3&amp;lt;/version&amp;gt;
	&amp;lt;executions&amp;gt;
	  &amp;lt;execution&amp;gt;
		&amp;lt;phase&amp;gt;prepare-package&amp;lt;/phase&amp;gt;
		&amp;lt;goals&amp;gt;
		  &amp;lt;goal&amp;gt;replace&amp;lt;/goal&amp;gt;
		&amp;lt;/goals&amp;gt;
	  &amp;lt;/execution&amp;gt;
	&amp;lt;/executions&amp;gt;
	&amp;lt;configuration&amp;gt;
	  &amp;lt;file&amp;gt;target/classes/banner.txt&amp;lt;/file&amp;gt;
	  &amp;lt;replacements&amp;gt;
		&amp;lt;replacement&amp;gt;
		  &amp;lt;token&amp;gt;#application.title#&amp;lt;/token&amp;gt;
		  &amp;lt;value&amp;gt;${project.artifactId}&amp;lt;/value&amp;gt;
		&amp;lt;/replacement&amp;gt;
		&amp;lt;replacement&amp;gt;
		  &amp;lt;token&amp;gt;#application.version#&amp;lt;/token&amp;gt;
		  &amp;lt;value&amp;gt;${project.version}&amp;lt;/value&amp;gt;
		&amp;lt;/replacement&amp;gt;
		&amp;lt;replacement&amp;gt;
		  &amp;lt;token&amp;gt;#build.time#&amp;lt;/token&amp;gt;
		  &amp;lt;value&amp;gt;${maven.build.timestamp}&amp;lt;/value&amp;gt;
		&amp;lt;/replacement&amp;gt;
	  &amp;lt;/replacements&amp;gt;
	&amp;lt;/configuration&amp;gt;
&amp;lt;/plugin&amp;gt;

&lt;/pre&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;
4. Summary&lt;/h2&gt;&lt;div&gt;In this short article, we have shown how you can set a customized banner for your Spring application.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/3922280946788795805/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/3922280946788795805?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/3922280946788795805'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/3922280946788795805'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/09/spring-boot-custom.html' title='Personalized Spring Boot Startups: Custom Banners and Build Information'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-6412830754469759132</id><published>2023-08-25T17:27:00.005+08:00</published><updated>2025-03-30T21:52:25.357+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-cloud"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-microservice"/><title type='text'>Kickstarting Your Spring Cloud Journey with the 2022 Skeleton Project</title><content type='html'>&lt;p&gt;In this article, I will share a composition of a Spring Cloud 2022 skeleton project with Spring Boot 3.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Introduction&lt;/h2&gt;&lt;p&gt;Spring Cloud 2022 is a long-awaited major release that builds on top of Spring Framework 6.x and Spring Boot 3.x. It needs at least Java 17 to run, which means it is fully compatible with Jakarta framework.&lt;/p&gt;&lt;p&gt;GIT repository is available below.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Spring Cloud Microservices&lt;/h2&gt;&lt;p&gt;The following services demonstrate a basic microservice architecture.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2.1 config-service&lt;/h3&gt;&lt;p&gt;This module depends on &lt;b&gt;spring-cloud-config-server&lt;/b&gt; and connects with a git repo to serve the configuration settings. This is very useful if you want your configurations to be loaded from a central repository.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2.2 discovery-server&lt;/h3&gt;&lt;p&gt;This module depends on &lt;b&gt;spring-cloud-starter-netflix-eureka-server&lt;/b&gt; where other services can register and be looked up.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2.3 api-gateway&lt;/h3&gt;&lt;p&gt;This module depends on &lt;b&gt;spring-cloud-starter-gateway&lt;/b&gt;. It is the gateway for all incoming requests and makes the necessary load balance forwarding to the actual service implementation.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2.4 mail-services&lt;/h3&gt;&lt;p&gt;This module depends on &lt;b&gt;spring-cloud-starter-config&lt;/b&gt;&amp;nbsp;to connect to the config server and &lt;b&gt;spring-cloud-starter-netflix-eureka-client&lt;/b&gt;&amp;nbsp;to register to the Eureka naming server.&lt;/p&gt;&lt;p&gt;It also depends on Webflux for providing a REST endpoint.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Running the Services&lt;/h2&gt;&lt;p&gt;Make the folder /config-repo a git repo by going inside the folder and running the command below.&lt;/p&gt;&lt;p&gt;&amp;gt;git init&lt;/p&gt;&lt;p&gt;The services must be run in this order&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;config-server&lt;/li&gt;&lt;li&gt;discovery-server&lt;/li&gt;&lt;li&gt;mail-services&lt;/li&gt;&lt;li&gt;api-gateway&lt;/li&gt;&lt;/ul&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Testing the Services&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;mail-services&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This service runs on port 8001.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The actual URL is &lt;i&gt;http://localhost:8001/mails/profiles/1&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;api-gateway&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This service runs on port 8001. It simply forwards the request to the mail-services.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The actual URL is &lt;i&gt;http://localhost:8000/mails/profiles/1&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;5. Repository&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuya/spring-cloud-2022&quot;&gt;https://github.com/czetsuya/spring-cloud-2022&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h2&gt;6. Development and Support&lt;/h2&gt;&lt;div&gt;Unlock the full coding experience! As a&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot; target=&quot;_blank&quot;&gt;GitHub Sponsor&lt;/a&gt;, you gain exclusive access to the code behind this article—start learning and building today!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I&#39;m available for contracting services and support. You can reach me at:&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/6412830754469759132/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/6412830754469759132?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/6412830754469759132'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/6412830754469759132'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/08/spring-cloud-2022-skeleton-project.html' title='Kickstarting Your Spring Cloud Journey with the 2022 Skeleton Project'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-733791230287112998</id><published>2023-06-28T16:49:00.005+08:00</published><updated>2023-06-28T16:50:53.781+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="java"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><title type='text'>Java XML Validation: How to Validate an XML Document against an XSD</title><content type='html'>&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;h2 style=&quot;text-align: left;&quot;&gt;1. Introduction&lt;/h2&gt;
&lt;div&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.&amp;nbsp;&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Generating Java Classes from XSD&lt;/h2&gt;
&lt;div&gt;For this exercise, we will be using an XSD from w3schools&amp;nbsp;
	&lt;a href=&quot;https://www.w3schools.com/xml/schema_example.asp&quot;&gt;https://www.w3schools.com/xml/schema_example.asp&lt;/a&gt;.
&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&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;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;IntelliJ&lt;/div&gt;
&lt;div&gt;
	&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHkRxu6LBQJZWmo1beebu74SoLLMAw791mjZr2SOQ4PWqD9G8h25U1W-cjWdGHVwH1mAxc5LI0KUwM9Ktn3APbyh_MPY1UMeOSQqtFPzXExHmz5i7jtpH373_Luej-IFeJaNilYYTvrBXnPD2wby6QjYZ8E3OzNEtxghY6U36mQccfGxFyJfe07UMAIAC9/s2057/intellij-jakarta-plugin.png&quot; style=&quot;clear: left; margin-bottom: 1em; margin-right: 1em; text-align: center;&quot;&gt;
		&lt;img border=&quot;0&quot; data-original-height=&quot;620&quot; data-original-width=&quot;2057&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHkRxu6LBQJZWmo1beebu74SoLLMAw791mjZr2SOQ4PWqD9G8h25U1W-cjWdGHVwH1mAxc5LI0KUwM9Ktn3APbyh_MPY1UMeOSQqtFPzXExHmz5i7jtpH373_Luej-IFeJaNilYYTvrBXnPD2wby6QjYZ8E3OzNEtxghY6U36mQccfGxFyJfe07UMAIAC9/s16000/intellij-jakarta-plugin.png&quot; /&gt;
	&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;Eclipse JAXB Classes Generator&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
	&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYLWt5MqXoyO37ymQTtriSc9VgT1GpuMA6MyzPeDm54aAiqK8_7DYzBbTvISu-0ZdYC3cbcez5lfPdaeW1HuIWIHRTYn2X3UuSQjIOrFlE4_7hnI8K_Hs3dBCJ0r9BbcXv4OBPXqmZvqIOBwQUDt9SCY0mLbtolR7zQWtO1XLVEgXHPYGXGiztwXMknf7F/s1121/jaxb-generator.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
		&lt;img border=&quot;0&quot; data-original-height=&quot;1020&quot; data-original-width=&quot;1121&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYLWt5MqXoyO37ymQTtriSc9VgT1GpuMA6MyzPeDm54aAiqK8_7DYzBbTvISu-0ZdYC3cbcez5lfPdaeW1HuIWIHRTYn2X3UuSQjIOrFlE4_7hnI8K_Hs3dBCJ0r9BbcXv4OBPXqmZvqIOBwQUDt9SCY0mLbtolR7zQWtO1XLVEgXHPYGXGiztwXMknf7F/s16000/jaxb-generator.png&quot; /&gt;
	&lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Generated Classes&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
	&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUZeneM8cF4-qRmm75X9gU5_Jk3hX6dIXr5wPnXqd8_lhCzy3lBPyIpzToJU-N2yxgO9a9VFrDoolMdBUA3UIqpgABMi90P9MhBAdwo9BfnSaKBXVIcy-ahwMyzliyAoj1gRDw8F0SGcgXSCEdt8taAOnZ5BWNbaeCs8KK5pmnxQd94es2vUUB0gAw0UAS/s2269/generate-java-from-xsd.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
		&lt;img border=&quot;0&quot; data-original-height=&quot;1440&quot; data-original-width=&quot;2269&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUZeneM8cF4-qRmm75X9gU5_Jk3hX6dIXr5wPnXqd8_lhCzy3lBPyIpzToJU-N2yxgO9a9VFrDoolMdBUA3UIqpgABMi90P9MhBAdwo9BfnSaKBXVIcy-ahwMyzliyAoj1gRDw8F0SGcgXSCEdt8taAOnZ5BWNbaeCs8KK5pmnxQd94es2vUUB0gAw0UAS/s16000/generate-java-from-xsd.png&quot; /&gt;
	&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Dependencies&lt;/h2&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&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;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Core Classes&lt;/h2&gt;
&lt;div&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;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.1 ResourceResolver&lt;/h3&gt;
&lt;div&gt;To import an external XSD, for example, if we will be using the XML digital signature.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;&amp;lt;xs:import namespace=&quot;http://www.w3.org/2000/09/xmldsig#&quot; schemaLocation=&quot;xmldsig-core-schema.xsd&quot;/&amp;gt;
&lt;/pre&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&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&#39;s are expected to be in the root of the classpath
    InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(&quot;xsd/&quot; + 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(&quot;\n&quot;);
        }
        textDataFromFile = stringBuilder.toString();

        return textDataFromFile;

      } catch (IOException e) {
        LOGGER.error(&quot;Fail reading from inputStream={}&quot;, e.getMessage());
        return null;
      }
    }
...
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.2 SaxErrorHandler&lt;/h3&gt;
&lt;div&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;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&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;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.3 Miscellaneous Classes&lt;/h3&gt;
&lt;div&gt;We will also be needing utility classes for:&lt;/div&gt;
&lt;div&gt;
	&lt;ul style=&quot;text-align: left;&quot;&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;div&gt;These classes are all available in the project.&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;4.4 XmlSchemaValidator Interface&lt;/h3&gt;
&lt;div&gt;And finally, the interface that pieces together all components to do the validation.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&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, &quot;&quot;);
      factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, &quot;&quot;);
      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;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;5. Source Code / Git Repository&lt;/h2&gt;
&lt;div&gt;The complete source code for this project is available for my sponsors at&amp;nbsp;
	&lt;a href=&quot;https://github.com/czetsuyatech/java-xml-validation-by-xsd&quot;&gt;https://github.com/czetsuyatech/java-xml-validation-by-xsd&lt;/a&gt;.
&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;Sponsorship link:&amp;nbsp;
	&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot;&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;Project Screenshot&lt;/div&gt;
&lt;div&gt;
	&lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
	&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW_ER_T4HVe-1lyK6mJ0bP8Imwjb5-43WgONpjlb93NDirVK8-kt3KgrY4WTuz4v07XvVlaY-bbmFPAWb6A9MRStv5z8F0J6PSofnWvXEbRGp-41kAmMefJ9rf9OnybpJ5AvdTrknSG_t0PNMSPRp8wug1V4i_jwYfafAS2k83QCef0Rkc33D0feLawcYL/s2463/Xml-vaildation-using-xsd.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
		&lt;img border=&quot;0&quot; data-original-height=&quot;1746&quot; data-original-width=&quot;2463&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW_ER_T4HVe-1lyK6mJ0bP8Imwjb5-43WgONpjlb93NDirVK8-kt3KgrY4WTuz4v07XvVlaY-bbmFPAWb6A9MRStv5z8F0J6PSofnWvXEbRGp-41kAmMefJ9rf9OnybpJ5AvdTrknSG_t0PNMSPRp8wug1V4i_jwYfafAS2k83QCef0Rkc33D0feLawcYL/s16000/Xml-vaildation-using-xsd.png&quot; /&gt;
	&lt;/a&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/733791230287112998/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/733791230287112998?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/733791230287112998'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/733791230287112998'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/06/java-validating-xml-document-against-an-xsd.html' title='Java XML Validation: How to Validate an XML Document against an XSD'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHkRxu6LBQJZWmo1beebu74SoLLMAw791mjZr2SOQ4PWqD9G8h25U1W-cjWdGHVwH1mAxc5LI0KUwM9Ktn3APbyh_MPY1UMeOSQqtFPzXExHmz5i7jtpH373_Luej-IFeJaNilYYTvrBXnPD2wby6QjYZ8E3OzNEtxghY6U36mQccfGxFyJfe07UMAIAC9/s72-c/intellij-jakarta-plugin.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-205117495696468907</id><published>2023-05-19T12:55:00.144+08:00</published><updated>2023-08-25T17:32:46.263+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-rest"/><title type='text'>Efficient HTTP Request Logging in Spring with Zalando: A Developer&#39;s Guide</title><content type='html'>&lt;p&gt;&amp;nbsp;Logging internal HTTP requests using RestTemplate with Zalando library.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Overview&lt;/h2&gt;&lt;p&gt;In some cases, it is critical that we log both internal and external HTTP requests. This is very useful for debugging communication between microservices. But this feature is already available with other Spring boot libraries. What I wanted to show in this exercise is the flexibility of logging or saving in a database of particular data.&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Prerequisites&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2.1 Dependencies&lt;/h3&gt;&lt;p&gt;For this exercise, I will use the following dependencies:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Spring Boot 3&lt;/li&gt;&lt;li&gt;logbook-spring-boot-starter v3.x&lt;/li&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2.2 The Spring Project&lt;/h3&gt;&lt;div&gt;The project that we will work on will run on Spring Boot 3.x.&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Hands On&lt;/h2&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;3.1 Controller&lt;/h3&gt;&lt;div&gt;We will start with our controller.&lt;/div&gt;&lt;p&gt;&lt;/p&gt;

&lt;pre class=&quot;brush: java&quot;&gt;@RestController
@Slf4j
@RequiredArgsConstructor
public class LoggingController {

  private final RestTemplate restTemplate;

  @GetMapping(&quot;/logs/internal-requests&quot;)
  public void logBody() {

    log.debug(&quot;Logging internal request&quot;);

    restTemplate.getForEntity(&quot;http://localhost:8080/call-me&quot;, Computer.class);
  }

  @GetMapping(&quot;/call-me&quot;)
  public Computer callMe() {
    log.debug(&quot;calling internal url&quot;);

    return Computer.builder()
        .brand(&quot;Apple&quot;)
        .model(&quot;Macbook Pro&quot;)
        .cpu(&quot;M2&quot;)
        .ram(32)
        .build();
  }
}

&lt;/pre&gt;
&lt;p&gt;
This class exposes a REST endpoint /logs/internal-requests that will trigger an HTTP request to another endpoint /call-me. This call will be intercepted by the logbook.&lt;/p&gt;&lt;p&gt;Let&#39;s see how it works.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;3.2 Web Configuration&lt;/h3&gt;&lt;p&gt;We need to create the following beans for the logbook to intercept the HTTP requests.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;@Configuration
@RequiredArgsConstructor
public class WebBeansConfig {

  private final Logbook logbook;

  @Bean
  public Logbook getLogbook() {
    return Logbook.builder()
        .strategy(new StatusAtLeastStrategy(200))
        .sink(new LogSink(new LogFormatter(), new LogWriter()))
        .build();
  }

  @Bean
  public RestTemplate restTemplate() {

    RestTemplate restTemplate = new RestTemplate();
    restTemplate.getInterceptors().add(getLogbookInterceptor());

    return restTemplate;
  }

  private LogbookClientHttpRequestInterceptor getLogbookInterceptor() {
    return new LogbookClientHttpRequestInterceptor(logbook);
  }
}

&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;3.3 Logbook and Logging Classes&lt;/h3&gt;&lt;div&gt;Here are the skeletons of the classes needed by the logbook.&lt;/div&gt;
&lt;p&gt;After the logbook intercepts the request, it will cause this class to perform pre and post request data manipulation.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;@RequiredArgsConstructor
public class LogSink implements Sink {

  private final LogFormatter formatter;
  private final LogWriter writer;

  @Override
  public void write(Precorrelation preCorrelation, HttpRequest request) throws IOException {

  }

  @Override
  public void write(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException {

  }

  @Override
  public void writeBoth(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException {
    writer.write(formatter.format(correlation.getId(), request, response));
  }
}
&lt;/pre&gt;
&lt;p&gt;This class extracts the necessary values from the HTTP request and response object. We can also deduce whether the request is incoming or outgoing.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;// Customizeable message model
public class LogFormatter {

  public Message format(String correlationId, HttpRequest request, HttpResponse response) throws IOException {

    return Message.builder()
        .correlationId(correlationId)
        .requestBody(request.getBodyAsString())
        .responseBody(response.getBodyAsString())

        .build();
  }
}
&lt;/pre&gt;
&lt;p&gt;Finally, this class logs the message object in the console. We can also save the Message object in the database by injecting a mapper and converting the Message to an entity, then inject a repository that we can use to insert the entity in the database.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;// Log writer
@Slf4j
public class LogWriter {

  // you can autowire a service here
  public void write(Message message) {
    log.debug(&quot;Http intercepted message={}&quot;, message);
  }
}

&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;4. GitHub Repository&lt;/h2&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Sponsor this blog:&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot;&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/logging-http-zalando/&quot;&gt;https://github.com/czetsuyatech/logging-http-zalando/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/205117495696468907/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/205117495696468907?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/205117495696468907'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/205117495696468907'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/05/efficient-http-request-logging-in.html' title='Efficient HTTP Request Logging in Spring with Zalando: A Developer&#39;s Guide'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-6696229571402068203</id><published>2023-05-19T12:53:00.494+08:00</published><updated>2023-12-01T13:27:30.923+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="keycloak"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-rest"/><title type='text'>Unlocking Google Calendar API with Keycloak Via Token Exchange: A Developer&#39;s Guide           </title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUi2cki8VoCskVhILbDLCGLThqC0XMg_HkiKKcm0WMsGVwLBT3U8LV2ak9ztDC_pSYBob81OjsvfWThZDFcxULcvsyMjB50sge-L1kUKjkVXGXJC3rztU6-Gdz3GzN3tGe8bdG2D_KHwdZKTkTfQ6r6zEPqAizbQMlUa7JiD98_MrSwTpXp1bka64rhQ/s3872/eric-rothermel-FoKO4DpXamQ-unsplash.jpg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2592&quot; data-original-width=&quot;3872&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUi2cki8VoCskVhILbDLCGLThqC0XMg_HkiKKcm0WMsGVwLBT3U8LV2ak9ztDC_pSYBob81OjsvfWThZDFcxULcvsyMjB50sge-L1kUKjkVXGXJC3rztU6-Gdz3GzN3tGe8bdG2D_KHwdZKTkTfQ6r6zEPqAizbQMlUa7JiD98_MrSwTpXp1bka64rhQ/s16000/eric-rothermel-FoKO4DpXamQ-unsplash.jpg&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Overview&lt;/h2&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.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;By following step-by-step instructions and best practices, you&#39;ll learn how to leverage Keycloak&#39;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;p&gt;&lt;/p&gt;
&lt;ul style=&quot;text-align: left;&quot;&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;p&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Google&lt;/h2&gt;
&lt;div&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;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;If you haven&#39;t done so, you can create a Google console account at&amp;nbsp; &lt;a href=&quot;https://cloud.google.com/cloud-console&quot;&gt;https://cloud.google.com/cloud-console&lt;/a&gt;.&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.1 Creating the Google Cloud Project&lt;/h3&gt;
&lt;div&gt;At the top left of your Google Cloud Console, click the project and click &lt;b&gt;New Project&lt;/b&gt;. &lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6AMZ4qT9K_oXqRi8ZThACUvNPeT-vKOyr-lzirmz1_3pb2yNW6OzSFM5AhspW8AgO1uOs67ylo1wHq-1LfDngcos0FFWUt4aB_KNq_JMPT-yLdtkpwceOq-Kz-o54l_xfwMkxKfXJw3cxfznwPju9WLoV13cz89t-n9IYheCcP8boSw734F014EGNZQ/s2573/1%20-%20create%20google%20cloud%20project.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;685&quot; data-original-width=&quot;2573&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6AMZ4qT9K_oXqRi8ZThACUvNPeT-vKOyr-lzirmz1_3pb2yNW6OzSFM5AhspW8AgO1uOs67ylo1wHq-1LfDngcos0FFWUt4aB_KNq_JMPT-yLdtkpwceOq-Kz-o54l_xfwMkxKfXJw3cxfznwPju9WLoV13cz89t-n9IYheCcP8boSw734F014EGNZQ/s16000/1%20-%20create%20google%20cloud%20project.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;div&gt;I call this project &quot;Lab&quot;.&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.2 Create OAuth2 Client&lt;/h3&gt;
&lt;div&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;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;With the &lt;b&gt;Lab&lt;/b&gt; project selected, find API &amp;amp; Services / Credentials in the menu. &lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiO-qnmJdu8vPm6DK0nnU81vi4-Ta-_cBRUa_Z6avPU14jIjv1YefJG770hwPYjGqmqK0rckvLDqxlANO9gZJtK5LJOwWBVF-O8k2Bhri0E-Ac_1GQ7-5UveuNl5-qbHippiOSs6zJbrKJB1um_0v1qBU5Q7FRoX5EeHx54TbEzz0TntgApx-unCjH25w/s1027/2-%20credentials.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1027&quot; data-original-width=&quot;1018&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiO-qnmJdu8vPm6DK0nnU81vi4-Ta-_cBRUa_Z6avPU14jIjv1YefJG770hwPYjGqmqK0rckvLDqxlANO9gZJtK5LJOwWBVF-O8k2Bhri0E-Ac_1GQ7-5UveuNl5-qbHippiOSs6zJbrKJB1um_0v1qBU5Q7FRoX5EeHx54TbEzz0TntgApx-unCjH25w/s16000/2-%20credentials.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Click &lt;b&gt;Create Credentials &lt;/b&gt;with the following inputs: &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Type: OAuth client ID&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Application type: Web application&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Name: Lab&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;There are two important entries in this section:&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Authorized Javascript origins&lt;/b&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;This should be the URL where the request for login will come from.&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Authorized redirect URIs&lt;/b&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;
    &lt;br /&gt;
  &lt;/b&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&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;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Some common redirect URIs.&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Amazon Cognito:&lt;/b&gt;&amp;nbsp;https://lab.auth.ap-southeast-1.amazoncognito.com/oauth2/idpresponse
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Postman:&lt;/b&gt;&amp;nbsp;https://www.getpostman.com/oauth2/callback
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Keycloak broker:&amp;nbsp;&lt;/b&gt;http://localhost:8080/realms/czetsuyatech/broker/google/endpoint
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Keycloak token:&lt;/b&gt;&amp;nbsp;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;When you have a redirect issue, this is one of the first things you may want to check.&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;This is how my Lab client looks like.&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUKj6bdQW1ygHFJXaJRtxfwCBgYGnGHhbZtnusa4e1mG5o1-zp5y2Ax0oTmgHWOw5-xghzIS32LQ0IQKWbEBftxAbZgwccW8KPxYZfUHHKBGnQMkjLI6dN2FyyVv8D8ULhg8RzVHlfeCVtHscUlH9jIHTv3KTRaFL_pdsrJUsgqwTLsoJMSFgouRDtNg/s1804/3%20-%20client.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1804&quot; data-original-width=&quot;1224&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUKj6bdQW1ygHFJXaJRtxfwCBgYGnGHhbZtnusa4e1mG5o1-zp5y2Ax0oTmgHWOw5-xghzIS32LQ0IQKWbEBftxAbZgwccW8KPxYZfUHHKBGnQMkjLI6dN2FyyVv8D8ULhg8RzVHlfeCVtHscUlH9jIHTv3KTRaFL_pdsrJUsgqwTLsoJMSFgouRDtNg/s16000/3%20-%20client.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;div style=&quot;text-align: left;&quot;&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;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVUvcRcv-zMqbq4Svq-g3bGF4ycaRLlBotK4WbCBYlhTYBi8ygW-TqeBehEK9hvd2oTIFyduUK7fDRJkOQa-SLUGj_COo1la4qWx3qXDLXFj_7Z4AjwPy_bjaG8qqD1hU0E-fDeNh0iHJIUAqqLO0ovov4cxfdboDyhUw3Arss6THNefKm8-A-rbxR5g/s1589/4%20-%20secret.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;735&quot; data-original-width=&quot;1589&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVUvcRcv-zMqbq4Svq-g3bGF4ycaRLlBotK4WbCBYlhTYBi8ygW-TqeBehEK9hvd2oTIFyduUK7fDRJkOQa-SLUGj_COo1la4qWx3qXDLXFj_7Z4AjwPy_bjaG8qqD1hU0E-fDeNh0iHJIUAqqLO0ovov4cxfdboDyhUw3Arss6THNefKm8-A-rbxR5g/s16000/4%20-%20secret.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.3 Setup OAuth Consent Screen&lt;/h3&gt;
&lt;div&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;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;b&gt;Non-sensitive scopes:&lt;/b&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;ul style=&quot;text-align: left;&quot;&gt;
    &lt;li&gt;auth-userinfo-email&lt;/li&gt;
    &lt;li&gt;auth-userinfo-profile&lt;/li&gt;
    &lt;li&gt;openid&lt;/li&gt;
  &lt;/ul&gt;
  &lt;div&gt;Don&#39;t forget to add a test user. We will use it to login later.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG4TvAimCy5__jQ-r1zOMVGjbZRQluKPRIFvvqCmnfJV9-9TKJ5AEVPyZkOYdhlXZGVlJg4UX445qwnll8BWuVQT7O_pTG-od6Cs8n2DCzt0bSMYOlnkPbVmWC3b3fYkmpAmxXCJ4Du3pu7WkiyqyAhtw75jt8lIeE-jyGb1i4hqizIOGTP1tarLPKJQ/s1174/5%20-%20test%20users.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;519&quot; data-original-width=&quot;1174&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG4TvAimCy5__jQ-r1zOMVGjbZRQluKPRIFvvqCmnfJV9-9TKJ5AEVPyZkOYdhlXZGVlJg4UX445qwnll8BWuVQT7O_pTG-od6Cs8n2DCzt0bSMYOlnkPbVmWC3b3fYkmpAmxXCJ4Du3pu7WkiyqyAhtw75jt8lIeE-jyGb1i4hqizIOGTP1tarLPKJQ/s16000/5%20-%20test%20users.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Keycloak&lt;/h2&gt;
&lt;div&gt;For this exercise, we need to create a new Keycloak realm and client. We must enable authentication in the client.&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;3.1 Client&lt;/h3&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8rDouX4miD7WXdMl7Zm3b7sIA9sf9r-MfpOvE1PtqzUxDpnU8l3nf2lTihKJSy0dtiRL80TZrcxuEO1aNZuz8joi4_iBslDwYE-mzdwpzpnm0mFhMpHx-xuzZB20OHjfyt_212IgWdD76EmFlj99qaHlWKkx1ZH__3d8vPhkpSyorW2oiVqoFIxzuEw/s1965/6-%20keycloak%20client.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;721&quot; data-original-width=&quot;1965&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8rDouX4miD7WXdMl7Zm3b7sIA9sf9r-MfpOvE1PtqzUxDpnU8l3nf2lTihKJSy0dtiRL80TZrcxuEO1aNZuz8joi4_iBslDwYE-mzdwpzpnm0mFhMpHx-xuzZB20OHjfyt_212IgWdD76EmFlj99qaHlWKkx1ZH__3d8vPhkpSyorW2oiVqoFIxzuEw/s16000/6-%20keycloak%20client.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;span style=&quot;font-weight: normal;&quot;&gt;Enabling authentication will give us access to Keycloak&#39;s client and secret, which we will need later for testing. Take note of it.&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;span style=&quot;font-weight: normal;&quot;&gt;
    &lt;br /&gt;
  &lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;span style=&quot;font-weight: normal;&quot;&gt;We also need to download the Adapter config of our newly created client.&lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;span style=&quot;font-weight: normal;&quot;&gt;
    &lt;br /&gt;
  &lt;/span&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
    &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0AVatzom3hQdV1ynaWluHacbXHIebTkjqsSBjcFu5SUw8Rz--hVvSYuh4ASYjSLQMknbZBr7KrHm_nnRHyEUOGlpbfYZLlrieDbEfbVN7wj27YUeDFj2ZNmhIOUbLfATypBpaW6U5boqnzsxEgZ7zXW_6xk2BQD9CL9GzRGgvMsPHbrBWHXbPCbBGXg/s3032/7%20-%20download%20adapter%20config.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
      &lt;img border=&quot;0&quot; data-original-height=&quot;961&quot; data-original-width=&quot;3032&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0AVatzom3hQdV1ynaWluHacbXHIebTkjqsSBjcFu5SUw8Rz--hVvSYuh4ASYjSLQMknbZBr7KrHm_nnRHyEUOGlpbfYZLlrieDbEfbVN7wj27YUeDFj2ZNmhIOUbLfATypBpaW6U5boqnzsxEgZ7zXW_6xk2BQD9CL9GzRGgvMsPHbrBWHXbPCbBGXg/s16000/7%20-%20download%20adapter%20config.png&quot; /&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;br /&gt;
  &lt;span style=&quot;font-weight: normal;&quot;&gt;
    &lt;br /&gt;
  &lt;/span&gt;
&lt;/div&gt; The downloaded config should look like this. Save it as we will use it in our Spring Boot application later.
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&gt;{
  &quot;realm&quot;: &quot;czetsuyatech&quot;,
  &quot;auth-server-url&quot;: &quot;http://localhost:8080/&quot;,
  &quot;ssl-required&quot;: &quot;external&quot;,
  &quot;resource&quot;: &quot;web-front&quot;,
  &quot;credentials&quot;: {
    &quot;secret&quot;: &quot;BxYWD11qCAW6ZybPTy6b9M8ej0thTxxx&quot;
  },
  &quot;confidential-port&quot;: 0
}&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;3.2 Identity Provider&lt;/h3&gt;
&lt;div&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;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;So login to Keycloak as admin, click &lt;b&gt;Identity Providers&lt;/b&gt; on the left side, Click &lt;b&gt;Add Provider&lt;/b&gt;, and select &lt;b&gt;Google.&lt;/b&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;In the Client ID and secret fields, enter the values that we saved when we were configuring the Google client earlier.&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;Under &lt;b&gt;Advance settings&lt;/b&gt;, &lt;b&gt;&lt;/b&gt;change the value of Scopes to &quot; &lt;i&gt;email profile openid https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/gmail.readonly&lt;/i&gt;&quot;, without double quotes. This will give us permission to call list endpoints from Calendar and Gmail APIs. &lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG_jVl5puTUVszwLu1XZtxuCbAmgshi3KoEKjGTiya7sDaLFzW8xYJZp5eRj6fKsiap4xHDuEZiOo5qLzEtMAgWVnv6uiMaOADnlOPMjAJchfnIA1-ffAJCMzWr6R-tNP1saarj98zBPmGJErmo7duccOPGxJcrV4cL1DYIvLmLXv5CB9_s_LK0fL5og/s2057/8%20-%20Google%20identity%20provider.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1625&quot; data-original-width=&quot;2057&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG_jVl5puTUVszwLu1XZtxuCbAmgshi3KoEKjGTiya7sDaLFzW8xYJZp5eRj6fKsiap4xHDuEZiOo5qLzEtMAgWVnv6uiMaOADnlOPMjAJchfnIA1-ffAJCMzWr6R-tNP1saarj98zBPmGJErmo7duccOPGxJcrV4cL1DYIvLmLXv5CB9_s_LK0fL5og/s16000/8%20-%20Google%20identity%20provider.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;3.3 Permissions&lt;/h3&gt;
&lt;div&gt;In this section, we will give our client permission to exchange tokens with the newly created Google identity provider.&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;On the identity provider page, click the &lt;b&gt;Permissions&lt;/b&gt; tab. Toggle &lt;b&gt;Permissions enabled&lt;/b&gt;&amp;nbsp;to &lt;b&gt;ON&lt;/b&gt;. Under the Permission list, click &lt;b&gt;token-exchange.&lt;/b&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;b&gt;
    &lt;br /&gt;
  &lt;/b&gt;
&lt;/div&gt;
&lt;div&gt;Set the Decision strategy to &lt;b&gt;Unanimous&lt;/b&gt;. Click Save. &lt;/div&gt;
&lt;div&gt;
  &lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
    &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhRNe8jZVRH_jdMA53XFmQR76LoVDUUO2dMF-LOs-kgI6KTQqO2BFDb4fA29OJkRN0WSH24ujs2t2mqF8z-0eVSvC-jDgzYh3LV0vz0UXEhdMRb3ck-ZhWlibqhCKBjFtJD4Aspfb0dfObDeBcHzwKmFIic8sy0Bjdg5xLbOX5olP63A4NWjUSLHic6g/s2160/9%20-%20permission.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
      &lt;img border=&quot;0&quot; data-original-height=&quot;1607&quot; data-original-width=&quot;2160&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhRNe8jZVRH_jdMA53XFmQR76LoVDUUO2dMF-LOs-kgI6KTQqO2BFDb4fA29OJkRN0WSH24ujs2t2mqF8z-0eVSvC-jDgzYh3LV0vz0UXEhdMRb3ck-ZhWlibqhCKBjFtJD4Aspfb0dfObDeBcHzwKmFIic8sy0Bjdg5xLbOX5olP63A4NWjUSLHic6g/s16000/9%20-%20permission.png&quot; /&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;br /&gt;
  &lt;b&gt;
    &lt;br /&gt;
  &lt;/b&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;span style=&quot;font-weight: normal;&quot;&gt;Next, we need to create the permission scope. At the top-left of the page, click &lt;/span&gt;
  &lt;b&gt;Client details&lt;/b&gt;. It should redirect you to the Authorization tab. Open the &lt;b&gt;Policies&lt;/b&gt;&amp;nbsp;sub-tab. Create a new policy with the following values:
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Type: Client&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Name: google-token-exchange&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Clients: web-front (the Keycloak client we created earlier)&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Logic: Positive&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbZ1RRP9nHg6irXBBLh_621hwV7N8asGziHQMBZIg6rmLIxvQ_dOmwLoS4p5Qsu5P_4mdjAmUMvLaUugP62BP6MiPINZhOV7GXZF4WoMrJHTqsGcGVfPHppxMasvnmcF6KEfvdsgrlj3Hms5fBfaQAgtJccTiLYyJttUBAWClw4TSdu-pKmlzV_iP4lA/s2151/10%20-%20policy.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1071&quot; data-original-width=&quot;2151&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbZ1RRP9nHg6irXBBLh_621hwV7N8asGziHQMBZIg6rmLIxvQ_dOmwLoS4p5Qsu5P_4mdjAmUMvLaUugP62BP6MiPINZhOV7GXZF4WoMrJHTqsGcGVfPHppxMasvnmcF6KEfvdsgrlj3Hms5fBfaQAgtJccTiLYyJttUBAWClw4TSdu-pKmlzV_iP4lA/s16000/10%20-%20policy.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Go back to your Google identity provider&#39;s permission page and set the &lt;b&gt;Policies&lt;/b&gt; to the one we just created. &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbIxPSZk0i78CtGqzWR2Refx5Ii3fhCb5-mGgk4SWhHAJzYqL4VeeZnZTYClTJEyYYojrVh9U79iNo0SZ-QARZPrlZBKWQwQwEp8xL7rdwAldCmWs6dZRwVN_LPNSwj8pnB3ayyC2wFh06OcRYy1vXqtsag6QAHz1arigyBZg8v__Wmoshwh5itjBqkw/s2174/11%20-%20policies.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1615&quot; data-original-width=&quot;2174&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbIxPSZk0i78CtGqzWR2Refx5Ii3fhCb5-mGgk4SWhHAJzYqL4VeeZnZTYClTJEyYYojrVh9U79iNo0SZ-QARZPrlZBKWQwQwEp8xL7rdwAldCmWs6dZRwVN_LPNSwj8pnB3ayyC2wFh06OcRYy1vXqtsag6QAHz1arigyBZg8v__Wmoshwh5itjBqkw/s16000/11%20-%20policies.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;The setup should now be ready to exchange tokens.&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Testing&lt;/h2&gt;
&lt;div&gt;To test the exchange of tokens between Keycloak and Google, I have created a Postman collection where we can:&lt;/div&gt;
&lt;div&gt;
  &lt;ul style=&quot;text-align: left;&quot;&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;div&gt;Before we proceed with the testing, we need to run a Keycloak instance. I have prepared a docker-compose to do that.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieNAYzR8VFEjAuemfdEacXnhVeXkoAMC3cAu38N1Vy3tp1w_wmf1rlLJQJGMNSpjN_g-u9lKB8e84iw-MvTtzMbJ74DrM__mqI9mNLl81ATx65KBbbSoeyLfgOKlzDr_lQyMVGot-_CvSOI3Zp7EkjNX0GoxGNMFOAL3HQ7TV-RDBuAETZz2qh016jhQ/s3334/12%20-%20keycloak%20docker%20compose.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1189&quot; data-original-width=&quot;3334&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieNAYzR8VFEjAuemfdEacXnhVeXkoAMC3cAu38N1Vy3tp1w_wmf1rlLJQJGMNSpjN_g-u9lKB8e84iw-MvTtzMbJ74DrM__mqI9mNLl81ATx65KBbbSoeyLfgOKlzDr_lQyMVGot-_CvSOI3Zp7EkjNX0GoxGNMFOAL3HQ7TV-RDBuAETZz2qh016jhQ/s16000/12%20-%20keycloak%20docker%20compose.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;We need to authenticate our user, so create a new &lt;b&gt;POST&lt;/b&gt; request in Postman and use OAuth2 authorization. Set the URL to&amp;nbsp;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token. &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlUM2BIIWsTxhqXeCqBjtXbMvnb-RJn7oCjc-_hk-RMIFOIERjIC5HrGcR7xWiCRuV8y6WRJAL3iybLvY5yQzhr7GfUkWgjYf1G81RDORqQ51OXX3d2ExfRXISrpMPrwjeUUh4DAuR0bPGSZsHuDeMi7m75Ib-BqA4ubudif1xbQc8-wX0f1OEI1gClA/s2800/13%20-%20postman%20oauth.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1610&quot; data-original-width=&quot;2800&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlUM2BIIWsTxhqXeCqBjtXbMvnb-RJn7oCjc-_hk-RMIFOIERjIC5HrGcR7xWiCRuV8y6WRJAL3iybLvY5yQzhr7GfUkWgjYf1G81RDORqQ51OXX3d2ExfRXISrpMPrwjeUUh4DAuR0bPGSZsHuDeMi7m75Ib-BqA4ubudif1xbQc8-wX0f1OEI1gClA/s16000/13%20-%20postman%20oauth.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Be sure to set the client id and secret to the ones we saved earlier when we created the Keycloak client.&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;For the truncated values:&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Callback URL:&lt;/b&gt;&amp;nbsp;https://www.getpostman.com/oauth2/callback
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Auth URL:&lt;/b&gt;&amp;nbsp;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/auth
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;b&gt;Access Token URL:&lt;/b&gt;&amp;nbsp;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Click &lt;b&gt;Get New Access Token&lt;/b&gt;. It should redirect us to a Google login page, use the test email address that you added earlier when creating the Google client. &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&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;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx34r4PO6qea9WbLXIu2I2QovUisu-VoBXuV522XLZZL-Ir3m1XcCmhivgsb4rpiEK4denejgW3g2UhpIg3t3XaJp34oMa6p0-v-glm7KmYI33DF5Lauw2k7EQnYRm5m3Da7sx7HT85kY1wuI3VpzAAjpYg7LMy2JiPNfw7yLDILoMpCVah-Tb024tOQ/s1372/14%20-%20google%20scopes.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;1372&quot; data-original-width=&quot;1234&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx34r4PO6qea9WbLXIu2I2QovUisu-VoBXuV522XLZZL-Ir3m1XcCmhivgsb4rpiEK4denejgW3g2UhpIg3t3XaJp34oMa6p0-v-glm7KmYI33DF5Lauw2k7EQnYRm5m3Da7sx7HT85kY1wuI3VpzAAjpYg7LMy2JiPNfw7yLDILoMpCVah-Tb024tOQ/s16000/14%20-%20google%20scopes.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Click &lt;b&gt;Allow&lt;/b&gt;. &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Copy the access token and paste it on a &lt;b&gt;bearerToken&lt;/b&gt; variable. The succeeding requests will have Authorization Type = Bearer and will use this value. &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEish8isAvM6jo2JaKAY5HhADLCkK45AVmzqo6t-gD1HER6LwugstI0hgafbN3PmzBe-jFfoaEEFGEGWlvjD4lHNtMq6cpeL83DJ9iOdk9DcrIy1VswBxNde6oc6e_ZvA2jqbzR0jfWFIKeGnykgB_OC5Lz3PHCb9NSmdoJ5fgYz_dTXxrs7ebryYOO0PQ/s1641/15%20-%20bearer%20token.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;618&quot; data-original-width=&quot;1641&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEish8isAvM6jo2JaKAY5HhADLCkK45AVmzqo6t-gD1HER6LwugstI0hgafbN3PmzBe-jFfoaEEFGEGWlvjD4lHNtMq6cpeL83DJ9iOdk9DcrIy1VswBxNde6oc6e_ZvA2jqbzR0jfWFIKeGnykgB_OC5Lz3PHCb9NSmdoJ5fgYz_dTXxrs7ebryYOO0PQ/s16000/15%20-%20bearer%20token.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/h2&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;The Body of the request must have the following URL encoded parameters:&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioXtF-u3ayG0vEGoRfUR8FZPtagvTqXLst3c760V9SCbKTt7OAygBiaZ15U-SqGFBBvr45N3X1XwvfhEdH-k-Mk_YJv8bhmqv8g_IKUVwaSqF6UDFvxDH0oHEJYOmFn8uDcZi-MbkxOkhPGJUyT7BiX1RhevioNsdcz2TBXuGw2iUFKfbN6PtAaNRfCA/s1737/16%20-%20exchange%20token%20endpoint.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;889&quot; data-original-width=&quot;1737&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioXtF-u3ayG0vEGoRfUR8FZPtagvTqXLst3c760V9SCbKTt7OAygBiaZ15U-SqGFBBvr45N3X1XwvfhEdH-k-Mk_YJv8bhmqv8g_IKUVwaSqF6UDFvxDH0oHEJYOmFn8uDcZi-MbkxOkhPGJUyT7BiX1RhevioNsdcz2TBXuGw2iUFKfbN6PtAaNRfCA/s16000/16%20-%20exchange%20token%20endpoint.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;Click &lt;b&gt;Send&lt;/b&gt;. It should return with the following response. &lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
  &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4SjkkopcVHJztEKYGEIdYH87yDBuNOq-nATebe7k4dBlidgw2QA_rAgmnZr3QDf-BSMZtvG-uorNqYn6T3uCq36GiaqUAfAw_lZK1JIFbqrsSjgSNdye2bK3rHeAt7BrqjRdSjXB6dfK3b4LDfv5BC3hboa6f55ijNeg1HRAWv0QWAVaBlXPX2Lybyw/s2597/17%20-%20exchange%20token%20response.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;
    &lt;img border=&quot;0&quot; data-original-height=&quot;600&quot; data-original-width=&quot;2597&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4SjkkopcVHJztEKYGEIdYH87yDBuNOq-nATebe7k4dBlidgw2QA_rAgmnZr3QDf-BSMZtvG-uorNqYn6T3uCq36GiaqUAfAw_lZK1JIFbqrsSjgSNdye2bK3rHeAt7BrqjRdSjXB6dfK3b4LDfv5BC3hboa6f55ijNeg1HRAWv0QWAVaBlXPX2Lybyw/s16000/17%20-%20exchange%20token%20response.png&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;This is the token that we can use to access Google APIs.&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;br /&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&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;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;5. Spring Project&lt;/h2&gt;
&lt;div&gt;This project demonstrates the token-exchange feature available on Keycloak. With this, we can exchange Keycloak for Google&#39;s token, which we can use to call Google APIs.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;span style=&quot;color: red;&quot;&gt;&lt;b&gt;Note:&lt;/b&gt; This project is only available for my sponsors&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;span style=&quot;color: red;&quot;&gt;&lt;i&gt;https://github.com/sponsors/czetsuya&lt;/i&gt;&lt;/span&gt;&lt;i&gt;&lt;span style=&quot;color: red;&quot;&gt;.&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.1 Available Features&lt;/h3&gt;
&lt;div&gt;
  &lt;div&gt;
    &lt;ul style=&quot;text-align: left;&quot;&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;div&gt;The heart of the application exchanges tokens between Keycloak and Google.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot; style=&quot;text-align: left;&quot;&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(&quot;Content-Type&quot;, MediaType.APPLICATION_FORM_URLENCODED.toString());
    headers.add(&quot;Accept&quot;, MediaType.APPLICATION_JSON.toString());

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

    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;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;5.2 Testing&lt;/h3&gt;&lt;div&gt;If you have subscribed as my &lt;b&gt;sponsor&lt;/b&gt;, you will be given access to my repository and proceed with this testing.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&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;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&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;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Get Calendar Events Response&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-_-Qx4s9KkeTRkfsR0iw9NwMzT-msQpuj52mIVb15hSm74oCfV7TMpyTSIVWIeCFHlqkz90yr7we93I5tqy1yeL5cpQa40FsyQp5jsxNI-D-8nx3tPCrF5e0Y5F-hzVj789HAuVrTOYjw3gmW5Dlu30KD2uQPlupsKi3K__V7RZVjJgv74Khr0lWxLw/s2138/19%20-%20get%20calendar%20entries.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1742&quot; data-original-width=&quot;2138&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-_-Qx4s9KkeTRkfsR0iw9NwMzT-msQpuj52mIVb15hSm74oCfV7TMpyTSIVWIeCFHlqkz90yr7we93I5tqy1yeL5cpQa40FsyQp5jsxNI-D-8nx3tPCrF5e0Y5F-hzVj789HAuVrTOYjw3gmW5Dlu30KD2uQPlupsKi3K__V7RZVjJgv74Khr0lWxLw/s16000/19%20-%20get%20calendar%20entries.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Get Gmail Messages&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8xeI2Wob9ek19ohSghnoNdcar8sYue-7GcPmXyYNgIpmOA5QmWglkPyna7UC5lPhuIC9y9_F3WR2E2m1P8vqj2oZJmkhWF35CzLqHuvdWVtsmePww-ZNP37jUAh2Rec7ghdHdbM0fHUbBwtEKMEovzWw_tnXBbYAZNfLypCpSooL1XKe1sPzqdqYQfg/s2030/20%20-%20get%20email%20messages.png&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1850&quot; data-original-width=&quot;2030&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8xeI2Wob9ek19ohSghnoNdcar8sYue-7GcPmXyYNgIpmOA5QmWglkPyna7UC5lPhuIC9y9_F3WR2E2m1P8vqj2oZJmkhWF35CzLqHuvdWVtsmePww-ZNP37jUAh2Rec7ghdHdbM0fHUbBwtEKMEovzWw_tnXBbYAZNfLypCpSooL1XKe1sPzqdqYQfg/s16000/20%20-%20get%20email%20messages.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;6. Source Code&lt;/h2&gt;
&lt;div&gt;All the resources used in this article and demo are available on the GitHub repository https://github.com/czetsuyatech/auth-exchange.&lt;/div&gt;
&lt;div&gt;
  &lt;ul style=&quot;text-align: left;&quot;&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;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;7. References&lt;/h2&gt;
&lt;div&gt;
  &lt;ul style=&quot;text-align: left;&quot;&gt;
    &lt;li&gt;https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange&lt;/li&gt;&lt;li&gt;https://www.keycloak.org/docs/latest/server_development/index.html#retrieving-external-idp-tokens&lt;/li&gt;&lt;li&gt;https://developers.google.com/calendar&lt;/li&gt;&lt;li&gt;https://developers.google.com/gmail/api/guides&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/6696229571402068203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/6696229571402068203?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/6696229571402068203'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/6696229571402068203'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/05/keycloak-exchange-token-with-google-for-calling-api.html' title='Unlocking Google Calendar API with Keycloak Via Token Exchange: A Developer&#39;s Guide           '/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUi2cki8VoCskVhILbDLCGLThqC0XMg_HkiKKcm0WMsGVwLBT3U8LV2ak9ztDC_pSYBob81OjsvfWThZDFcxULcvsyMjB50sge-L1kUKjkVXGXJC3rztU6-Gdz3GzN3tGe8bdG2D_KHwdZKTkTfQ6r6zEPqAizbQMlUa7JiD98_MrSwTpXp1bka64rhQ/s72-c/eric-rothermel-FoKO4DpXamQ-unsplash.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-5119694218082522757</id><published>2023-05-07T07:28:00.007+08:00</published><updated>2023-05-20T17:10:49.452+08:00</updated><title type='text'>Czetsuya Tech Sponsorship Benefits</title><content type='html'>&lt;h3 style=&quot;text-align: left;&quot;&gt;Bronze&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Sponsorship badge&lt;/li&gt;&lt;li&gt;NO access to private repositories&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Silver&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;You will have access to one of my private repositories&lt;/li&gt;&lt;li&gt;Does not include the repositories in the higher tiers&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Gold&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;You will have access to a set of my selected private repositories&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Pre-Quarkus Custom Keycloak&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/ct-keycloak-iam&quot;&gt;https://github.com/czetsuyatech/ct-keycloak-iam&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/ct-keycloak-spis&quot;&gt;https://github.com/czetsuyatech/ct-keycloak-spis&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/ct-aws-cognito-spring-security&quot;&gt;https://github.com/czetsuyatech/ct-aws-cognito-spring-security&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/ct-keycloak-spring-security&quot;&gt;https://github.com/czetsuyatech/ct-keycloak-spring-security&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/ct-iam-spring-security&quot;&gt;https://github.com/czetsuyatech/ct-iam-spring-security&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/java-x509certificate-validation&quot;&gt;https://github.com/czetsuyatech/java-x509certificate-validation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/java-xml-digital-signature&quot;&gt;https://github.com/czetsuyatech/java-xml-digital-signature&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/unified-kafka-events&quot;&gt;https://github.com/czetsuyatech/unified-kafka-events&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/unified-kafka-events-client&quot;&gt;https://github.com/czetsuyatech/unified-kafka-events-client&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/auth-exchange&quot;&gt;https://github.com/czetsuyatech/auth-exchange&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;Does not include the repositories in the higher tiers.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Diamond&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;You will have access to my HiveMaster set of repositories that provides multi-tenancy IAM using Keycloak and Spring.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Keycloak (&amp;gt;v21.0.2)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-iam&quot;&gt;https://github.com/czetsuyatech/hivemaster-iam&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-spring-security&quot;&gt;https://github.com/czetsuyatech/hivemaster-spring-security&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-spring-client&quot;&gt;https://github.com/czetsuyatech/hivemaster-spring-client&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-spis&quot;&gt;https://github.com/czetsuyatech/hivemaster-spis&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-keycloak-event-bridge&quot;&gt;https://github.com/czetsuyatech/hivemaster-keycloak-event-bridge&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-iam-services&quot;&gt;https://github.com/czetsuyatech/hivemaster-iam-services&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-clients-java&quot;&gt;https://github.com/czetsuyatech/hivemaster-clients-java&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/hivemaster-api-specs&quot;&gt;https://github.com/czetsuyatech/hivemaster-api-specs&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;div&gt;For contract services&amp;nbsp;&lt;a href=&quot;https://www.czetsuyatech.com/p/consultation-services.html&quot;&gt;https://www.czetsuyatech.com/p/consultation-services.html&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;i&gt;Keep coding, and keep supporting!&lt;/i&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/5119694218082522757/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/5119694218082522757?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5119694218082522757'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/5119694218082522757'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/05/czetsuya-tech-sponsorship-benefits.html' title='Czetsuya Tech Sponsorship Benefits'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-2383743819739616486</id><published>2023-05-06T17:55:00.014+08:00</published><updated>2023-05-07T13:12:08.502+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="java"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><title type='text'>Implementing X509 Certificate Validation in Java: A Step-by-Step Guide</title><content type='html'>&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 style=&quot;text-align: left;&quot;&gt;Overview&lt;/h2&gt;
&lt;div&gt;
  &lt;div&gt;An X.509 certificate is a digital certificate used to verify a particular entity&#39;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;/div&gt;
  &lt;div&gt;
    &lt;br /&gt;
  &lt;/div&gt;
  &lt;div&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;/div&gt;
  &lt;h2 style=&quot;text-align: left;&quot;&gt;Importance&lt;/h2&gt;
  &lt;div&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;/div&gt;
  &lt;div&gt;
    &lt;br /&gt;
  &lt;/div&gt;
  &lt;div&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;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Requirements&lt;/h2&gt;
&lt;div&gt;
  &lt;div&gt;You must have the following installed on your computer.&lt;/div&gt;
  &lt;div&gt;- Git&lt;/div&gt;
  &lt;div&gt;- Maven&lt;/div&gt;
  &lt;div&gt;- IDE (IntellIJ / Visual Studio)&lt;/div&gt;
  &lt;div&gt;- Java 17+&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h2 style=&quot;text-align: left;&quot;&gt;Dependencies&lt;/h2&gt;
  &lt;div&gt;This project uses the following dependencies.&lt;/div&gt;
  &lt;div&gt;- bcpkix-jdk15on - certificate revocation check&lt;/div&gt;
  &lt;div&gt;- spring-boot-starter-test (for testing)&lt;/div&gt;
  &lt;div&gt;- lombok (for logging)&lt;/div&gt;
  &lt;div&gt;- Certificates from https://www.digicert.com/kb/digicert-root-certificates.htm&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;Project Code&lt;/h2&gt;&lt;div&gt;The certificate validation process.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg4_ReIU4kjr9c7FMgel2sDqj1vWCb1HsQdb_HI94ywi7usn1bsveXpkzZa-lkuvjuSDgYhgE2tsFni-TfVqKy0BP-5yFexsLDfxvpJupMbcg2x7s5Cjnn0ESIgpPiSMxvxULmkYxQqmUT7XetSYmVRhpnnkNXIuKWky7cnswoSbpmAYw8vklqYxOf8Cw&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img data-original-height=&quot;438&quot; data-original-width=&quot;441&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg4_ReIU4kjr9c7FMgel2sDqj1vWCb1HsQdb_HI94ywi7usn1bsveXpkzZa-lkuvjuSDgYhgE2tsFni-TfVqKy0BP-5yFexsLDfxvpJupMbcg2x7s5Cjnn0ESIgpPiSMxvxULmkYxQqmUT7XetSYmVRhpnnkNXIuKWky7cnswoSbpmAYw8vklqYxOf8Cw=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;To implement the validation, we will introduce a bean where we can define the:&lt;/div&gt;
&lt;div&gt;
  &lt;ol style=&quot;text-align: left;&quot;&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;div&gt;Let&#39;s examine the code.&lt;/div&gt;
&lt;/div&gt;

&lt;div&gt;The CertificateBundle class is just a simple Java bean.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&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;/pre&gt;

&lt;div&gt;To validate the &lt;b&gt;certificate expiration&lt;/b&gt;, we need to extract the X509Certificate object and perform a time comparison against the value of getNotAfter(). Here, we&#39;re making sure that the current date is greater than the certificate&#39;s expiry date.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;Date expiresOn = cert.getNotAfter();
Date now = new Date();

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

&lt;div&gt;&lt;b&gt;Comparing digital signature against a PEM file.&lt;/b&gt;&amp;nbsp;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;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiaoKK27UU61MIL6aa4rk0j_rC83fpASrGEELGrYyeVHu4Emd8yzJdVG5Ba4vjbCFei9iZ2ERcXMCrWlSRQQ0EOuVgntpWGznjqs9BIlxZ6R7pX2bR99U19BB4bN0yHf9oqDMuWKNdLGG5I78iXDcE5kCrIbimOv52YGJUFnvUyESmSj0Eqk6HRbZR7ew&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img data-original-height=&quot;301&quot; data-original-width=&quot;437&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiaoKK27UU61MIL6aa4rk0j_rC83fpASrGEELGrYyeVHu4Emd8yzJdVG5Ba4vjbCFei9iZ2ERcXMCrWlSRQQ0EOuVgntpWGznjqs9BIlxZ6R7pX2bR99U19BB4bN0yHf9oqDMuWKNdLGG5I78iXDcE5kCrIbimOv52YGJUFnvUyESmSj0Eqk6HRbZR7ew=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;boolean match = Arrays.equals(cert1.getPublicKey().getEncoded(),
	cert2.getPublicKey().getEncoded());

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

&lt;div&gt;And finally, we do the &lt;b&gt;revocation check. &lt;/b&gt;This process allows us to know whether a website&#39;s certificate is trustworthy or not. It uses the root certificate that we used for signing as the trust anchor.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;try {
  List&amp;lt;X509Certificate&amp;gt; certs = Collections.singletonList(cb.getCertificate());
  CertificateFactory cf = CertificateFactory.getInstance(&quot;X509&quot;);
  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(&quot;Collection&quot;,
	  new CollectionCertStoreParameters(getX509Crls(cf, getCrl(cb.getCertificate())))));

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

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

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

} catch (InvalidAlgorithmParameterException | java.security.cert.CertificateException |
		 CRLException |
		 NoSuchAlgorithmException |
		 CertPathValidatorException |
		 IOException e) {
  throw new RevokedCertificateException(e);
}

&lt;/pre&gt;

&lt;h2 style=&quot;text-align: left;&quot;&gt;Repository&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/czetsuyatech/java-x509certificate-validation&quot;&gt;https://github.com/czetsuyatech/java-x509certificate-validation&lt;/a&gt;&amp;nbsp;- Available for my Silver sponsors:&amp;nbsp;&lt;a href=&quot;https://github.com/sponsors/czetsuya&quot;&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/2383743819739616486/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/2383743819739616486?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2383743819739616486'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/2383743819739616486'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/05/java-x059certificate-validation.html' title='Implementing X509 Certificate Validation in Java: A Step-by-Step Guide'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEg4_ReIU4kjr9c7FMgel2sDqj1vWCb1HsQdb_HI94ywi7usn1bsveXpkzZa-lkuvjuSDgYhgE2tsFni-TfVqKy0BP-5yFexsLDfxvpJupMbcg2x7s5Cjnn0ESIgpPiSMxvxULmkYxQqmUT7XetSYmVRhpnnkNXIuKWky7cnswoSbpmAYw8vklqYxOf8Cw=s72-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-7368669332546687435</id><published>2023-03-31T11:35:00.002+08:00</published><updated>2023-03-31T11:35:26.995+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="jpa"/><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-data"/><title type='text'>How to Use Lob Datatype in PostgreSQL</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Problem&lt;/h2&gt;
&lt;p&gt;Here&#39;s the typical code we use when creating a Lob column.&lt;/p&gt;
&lt;p&gt;Column definition&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;@NotNull
@Lob
@Column(name = &quot;message&quot;)
private String message;
&lt;/pre&gt;
&lt;p&gt;But when we create our tables we would encounter the error below, because PostgreSQL doesn&#39;t support lob.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: wrong column type encountered in column [message] in table [comm_messages]; found [text (Types#VARCHAR)], but expecting [oid (Types#CLOB)]
    at org.hibernate.tool.schema.internal.AbstractSchemaValidator.validateColumnType(AbstractSchemaValidator.java:167)
&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Solution&lt;/h2&gt;
&lt;p&gt;To resolve this problem we can implement several solutions.&lt;/p&gt;
&lt;p&gt;2.1 Set the column to TEXT in the table. With this approach, we need to manually edit the entity where the column is defined.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;@NotNull
@Lob
@Column(name = &quot;message&quot;, columnDefinition = &quot;TEXT&quot;)
private String message;
&lt;/pre&gt;
&lt;p&gt;2.2 If you are using Spring, you can set the non_contextual_creation to true in the application property file.&lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          lob:
            non_contextual_creation: true
&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/7368669332546687435/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/7368669332546687435?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/7368669332546687435'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/7368669332546687435'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/03/how-to-use-lob-datatype-in-postgresql.html' title='How to Use Lob Datatype in PostgreSQL'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6581802047354010081.post-1701375600949205754</id><published>2023-03-19T20:30:00.006+08:00</published><updated>2023-03-19T20:36:08.647+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="spring"/><category scheme="http://www.blogger.com/atom/ns#" term="spring-data"/><title type='text'>How to Use EntityListener for Effective Audit Trails in Spring</title><content type='html'>&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Overview&lt;/h2&gt;
&lt;div&gt;
  &lt;div&gt;An audit trail is important in persisting entities using JPA because it helps maintain data integrity and ensure compliance with regulatory requirements. An audit trail records a history of changes made to data over time, including who made the changes and when. By implementing an audit trail, you can track and verify any changes made to data, and identify any unauthorized or unexpected modifications. This can help detect and prevent data tampering, and provide transparency into data changes made by different users over time.&lt;/div&gt;
  &lt;div&gt;
    &lt;br /&gt;
  &lt;/div&gt;
  &lt;div&gt;In the context of JPA, audit trails can be implemented using various techniques, including adding auditable columns to database tables or using interceptors to capture changes made to entities before they are persisted in the database. By including an audit trail in your JPA application, you can maintain a comprehensive history of changes made to data, which can be useful for various purposes such as auditing, compliance, and troubleshooting.&lt;/div&gt;
  &lt;div&gt;
    &lt;br /&gt;
  &lt;/div&gt;
  &lt;div&gt;Overall, implementing an audit trail can provide greater transparency and accountability for data changes in your JPA application, which can help ensure data integrity and protect against unauthorized access or data tampering.&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Hands-on Coding&lt;/h2&gt;
&lt;div&gt;Spring-data-jpa provides a convenient solution for implementing audit trails in your Java applications, and in this tutorial, we&#39;ll show you how to use it to quickly get started with auditing your classes.&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.1 Auditable Entity&lt;/h3&gt;
&lt;div&gt;An abstract class where we will store the audit information such as date and user. It should be annotated with&amp;nbsp;AuditingEntityListener from spring-data-jpa. I&#39;m using Lombok dependency for getter and setter here.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;

@Data
@SuperBuilder
@MappedSuperclass
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity extends BaseEntity {

  @Column(name = &quot;created_at&quot;, nullable = false, updatable = false)
  @CreatedDate
  private LocalDateTime createdAt;

  @Column(name = &quot;updated_at&quot;, nullable = false)
  @LastModifiedDate
  private LocalDateTime updatedAt;

  @Column(name = &quot;created_by&quot;, nullable = false, updatable = false)
  @CreatedBy
  private String createdBy;

  @Column(name = &quot;updated_by&quot;, nullable = false)
  @LastModifiedBy
  private String updatedBy;
}
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.2 Auditor Information&lt;/h3&gt;
&lt;div&gt;A class where we can get the currently logged user using SecurityContextHolder or a constant for system user.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;

public class AuditorAwareImpl implements AuditorAware&amp;lt;String&gt; {

  @Override
  public Optional&amp;lt;String&gt; getCurrentAuditor() {
    return Optional.of(&quot;SYSTEM&quot;);
  }
}
&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;2.3 Jpa Configuration&lt;/h3&gt;
&lt;div&gt;We need to tell Spring context where to pull the auditor information as such we need to add this configuration.&lt;/div&gt;
&lt;pre class=&quot;brush: java&quot;&gt;

@Configuration
@EnableJpaAuditing(auditorAwareRef = &quot;auditorProvider&quot;)
@EnableTransactionManagement
public class JpaConfig {

  @Bean
  public AuditorAware&amp;lt;String&gt; auditorProvider() {
    return new AuditorAwareImpl();
  }

}
&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='https://www.czetsuyatech.com/feeds/1701375600949205754/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment/fullpage/post/6581802047354010081/1701375600949205754?isPopup=true' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/1701375600949205754'/><link rel='self' type='application/atom+xml' href='https://www.blogger.com/feeds/6581802047354010081/posts/default/1701375600949205754'/><link rel='alternate' type='text/html' href='https://www.czetsuyatech.com/2023/03/spring-data-audit-trail-using-entitylistener.html' title='How to Use EntityListener for Effective Audit Trails in Spring'/><author><name>czetsuya</name><uri>http://www.blogger.com/profile/05992265396469184599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>