<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="https://blog.cronn.de/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.cronn.de/" rel="alternate" type="text/html" /><updated>2025-09-25T17:59:10-05:00</updated><id>https://blog.cronn.de/feed.xml</id><title type="html">wir bloggen über software_</title><subtitle>Im cronn Blog findet ihr Artikel zu Softwareentwicklung mit den neusten Technologien, zu coolen UI/UX-Designs, automatisierten Tests, aber auch zum Life-Style und Work-Life-Balance - alles &quot;the cronn way&quot;.</subtitle><entry xml:lang="en"><title type="html">Performance Testing with k6: A Field Report</title><link href="https://blog.cronn.de/en/testing/2025/07/18/performance-testing-with-k6.html" rel="alternate" type="text/html" title="Performance Testing with k6: A Field Report" /><published>2025-07-18T00:00:00-05:00</published><updated>2025-07-18T00:00:00-05:00</updated><id>https://blog.cronn.de/en/testing/2025/07/18/performance-testing-with-k6</id><content type="html" xml:base="https://blog.cronn.de/en/testing/2025/07/18/performance-testing-with-k6.html">&lt;h3 id=&quot;project-context&quot;&gt;Project context&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://ga-lotse.de/&quot;&gt;GA-Lotse&lt;/a&gt; is a modular web application for health authorities which is intended to simplify internal documentation and external communication with citizens. Different departments are mapped in modules, which then can be configured by the health authorities. To ensure that the application meets highest security standards, the data is stored separately for each module. This and other security features – such as the Zero Trust principle – lead to intrinsic performance losses, which is why performance testing was an important part of the &lt;a href=&quot;https://www.cronn.de/referenzen/digitalisierung-gesundheitsamt-en&quot;&gt;project&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;selecting-the-load-testing-tool&quot;&gt;Selecting the load testing tool&lt;/h3&gt;

&lt;p&gt;It is often the case that you don’t have to implement everything yourself, so we looked for a tool which supports performance testing. Since we want to test a web application, the tool must allow browser testing. Our additional requirements were as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The ability to write the test code in TypeScript, as we also use TypeScript for the frontend of the application and the end-to-end tests&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Open-source availability of the tool&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Executability on a self-hosted server (not a pure cloud solution)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Good reporting to visualize the results of the tests for us and the developers.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After evaluating several tools, we decided on &lt;a href=&quot;https://grafana.com/docs/k6/latest/&quot;&gt;k6&lt;/a&gt;. k6 supports browser tests, enables development in TypeScript and, in combination with Grafana and through individually definable metrics, offers comprehensive reporting.&lt;/p&gt;

&lt;h3 id=&quot;our-setup&quot;&gt;Our setup&lt;/h3&gt;

&lt;p&gt;k6 runs the performance tests and generates some metrics, such as &lt;a href=&quot;https://web.dev/articles/ttfb&quot;&gt;TTFB&lt;/a&gt; or the duration of the individual requests. However, in order to visualize these and other test results, we needed even more tools. We chose &lt;a href=&quot;https://www.influxdata.com/&quot;&gt;InfluxDB&lt;/a&gt; as the database, as it is optimized for storing data in a time-resolved manner. To visualize the results, we used &lt;a href=&quot;https://grafana.com/oss/grafana&quot;&gt;Grafana-Dashboards&lt;/a&gt; because k6 belongs to Grafana and it provides an interface to InfluxDB. To query the data from the InfluxDB, we used the proprietary database query language &lt;a href=&quot;https://docs.influxdata.com/flux/v0/&quot;&gt;Flux&lt;/a&gt;. However, this is not a long-term solution as Flux will probably no longer be supported – or only supported to a limited extent – in the next major version. We decided to use the tools locally and package them in Docker containers in order to be able to run the tests hardware-independently and not be dependent on cloud providers. Alternatively, there is the option of using &lt;a href=&quot;https://grafana.com/products/cloud/k6/&quot;&gt;Grafana Cloud k6&lt;/a&gt;
to avoid installing the tools locally.&lt;/p&gt;

&lt;h3 id=&quot;performance-testing-with-k6&quot;&gt;Performance testing with k6&lt;/h3&gt;

&lt;p&gt;A test with k6 can be executed with a Javascript or TypeScript file (see example script).&lt;/p&gt;
&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Scenario&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;k6/options&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryBrowserTest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@/modules/browser/schoolEntryBrowserTest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryApiTest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@/modules/api/schoolEntryApiTest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Scenario&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;schoolEntryBrowser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;schoolEntryBrowserTestFunction&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;constant-vus&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;vus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;15m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chromium&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;schoolEntryApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;schoolEntryApiTestFunction&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ramping-vus&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;startVUs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;discardResponseBodies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;systemTags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;setupTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryBrowserTestFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryBrowserTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryApiTestFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryApiTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This script defines options for the test and the test functions to be executed. The options are defined as JSON. An important option which determines the course of the test is &lt;code class=&quot;highlighter-rouge&quot;&gt;scenarios&lt;/code&gt;. This is where executable scenarios can be defined, thus mapping the actual test.&lt;/p&gt;

&lt;p&gt;To define a scenario one must define a function to be executed, as well as the number of executing parallel users, which in k6 are called Virtual Users (VU). The total duration of the scenario can be determined by specifying time periods. In addition, ramps can be defined to increase or decrease the number of parallel users during the test. Another way to influence the course of the test is to set a time interval in which a specific number of VUs should go through the scenario.&lt;/p&gt;

&lt;p&gt;Several such scenarios can be defined for a test, which are then run using different configurations. To make this definition of the scenarios easier and faster than editing a long JSON file, we have developed a builder that dynamically creates the scenario configuration and makes it available on GitHub: &lt;a href=&quot;https://github.com/cronn/k6-scenario-builder&quot;&gt;https://github.com/cronn/k6-scenario-builder&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;our-findings&quot;&gt;Our findings&lt;/h3&gt;

&lt;p&gt;During testing, we noticed a few things which need to be taken into account. First of all, it makes sense to have a dedicated machine available to run the tests. Since performance is not only affected by the load of many simultaneous users, but also by the amount of data in the database, we created both short spike tests as well as test scenarios that have a runtime of several hours in order to constantly increase the amount of data and simulate a kind of time-lapse of the actual use of the application. These tests can be carried out much more comfortably by an external machine than on your own laptop.&lt;/p&gt;

&lt;p&gt;In addition, the execution of a test requires sufficient resources on the executing machine. Therefore, care should be taken to ensure that there are always free resources available during the execution of a test so as not to unintentionally influence the results. We noticed this when running browser tests with some VUs. Too many browsers open at the same time turned the machine into a bottleneck. Our solution to this is to define both scenarios and browser tests which depict the same user journey, but send the necessary requests directly to the backend in order to increase the load on the backend without accessing the browser. Such API scenarios are also well suited to quickly assemble a scenario and thus get an overview of the backend’s performance.&lt;/p&gt;

&lt;p&gt;Another insight we gained was to test in an environment which was as close to production as possible. After all, the configuration of an environment, especially a complex microservice cluster, can have significant impact on performance. In addition to running the tests from another machine and testing on a production-like environment, it was still important for us to enable testing entirely on our own laptop. This allows developers to independently develop new scenarios and provide easy access to databases and logs.&lt;/p&gt;

&lt;p&gt;It also occurred that we had exceeded professional limits by configuring our scenarios, especially during long tests. For example, we created an unrealistic number of appointments for one day or user, or even had too many users with the same permissions. Many different parameters can influence performance and should therefore be defined as early as possible, allowing us to avoid unnecessary test runs. Nevertheless, it was also important for us to deliberately exceed the known limits to test the limits of the application and then improve it where necessary. After all, the customer may not know their professional limits, or their limits might be reached through technical errors. The application should not become unusable because the user booked one appointment too many. One lesson learned was therefore to clarify professional limits at an early stage and to observe them in the tests.&lt;/p&gt;

&lt;h3 id=&quot;pros-and-cons-of-k6&quot;&gt;Pros and Cons of k6&lt;/h3&gt;

&lt;p&gt;We ran into problems from time to time during testing with k6. A significant limitation of developing performance tests with k6 is a lack of a debugger. k6 uses its own &lt;a href=&quot;https://github.com/grafana/sobek&quot;&gt;JavaScript engine&lt;/a&gt;
to execute the test code, and there is no built-in debugger. The Javascript engine also has other weaknesses which you should be aware of, such as that it does not support the popular fetch API. In the context of browser tests, methods such as &lt;em&gt;goto()&lt;/em&gt; are a weakness, as they do not always work reliably in combination with Chromium, which occasionally leads to timing problems. In addition, locators must be identified via XPaths, which is very susceptible to regression, as well as often unsightly and long. Finally, the documentation of k6 is often relatively short.&lt;/p&gt;

&lt;p&gt;However, k6 also has many advantages. The reporting in combination with InfluxDB and Grafana works very well. Meaningful plots can be quickly created in such a setup without much prior knowledge and then be displayed in a dashboard so that the test results can be analyzed and communicated. In addition, the parallel execution of different scenarios, each of which is also executed with parallel virtual users, works very well. It allows you to create complex scenarios which map different types of performance tests, such as load tests, spike tests, and soak tests. The fact that the test options (and especially the scenarios) are described in JSON is an advantage as it provides a smooth transition to the Typescript code. You also have the option of running the browser tests in headful mode, so that problems can be detected and fixed during execution.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;

&lt;p&gt;Since we had constantly developed both our tests and setup during the test phase, an iterative approach paid off for us. We started with two simple scenarios for application-critical modules. In these initial scenarios, we realized that we needed more metrics and plots in our reports to analyze the results. Iteratively, we then added metrics to our tests and visualized them in the Grafana board. These metrics included information such as the duration of requests, the loading times of certain pages, or even the CPU and RAM usage of the executing machine. The duration of individual requests was particularly important for us, but which information is relevant depends on the application. Metric types built into k6 allow the collection of information to be flexibly designed. Working with k6 has shown us both strengths and weaknesses of the tool. Whether k6 is the best choice certainly depends on the use case, but for us it was a suitable tool despite some significant weaknesses.&lt;/p&gt;</content><author><name>simonBiwer</name></author><summary type="html">We're sharing our experience with performance testing in the GA-Lotse project – using a setup with k6, Grafana, InfluxDB, and TypeScript.</summary></entry><entry xml:lang="de"><title type="html">Performance-Testing mit k6: Ein Erfahrungsbericht</title><link href="https://blog.cronn.de/de/testing/2025/07/18/performance-testing-mit-k6.html" rel="alternate" type="text/html" title="Performance-Testing mit k6: Ein Erfahrungsbericht" /><published>2025-07-18T00:00:00-05:00</published><updated>2025-07-18T00:00:00-05:00</updated><id>https://blog.cronn.de/de/testing/2025/07/18/performance-testing-mit-k6</id><content type="html" xml:base="https://blog.cronn.de/de/testing/2025/07/18/performance-testing-mit-k6.html">&lt;h3 id=&quot;projektkontext&quot;&gt;Projektkontext&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://ga-lotse.de/&quot;&gt;GA-Lotse&lt;/a&gt; (Gesundheitsamt-Lotse) ist eine modular aufgebaute Webanwendung für Gesundheitsämter, die die interne Dokumentation und externe Kommunikation mit Bürgerinnen und Bürgern vereinfachen soll. Verschiedene Abteilungen eines Gesundheitsamtes sind in Modulen abgebildet, die für Gesundheitsämter konfiguriert werden können. Damit die Anwendung höchsten Sicherheitsstandards genügt, werden die Daten für jedes Modul separat gespeichert. Dies und weitere Sicherheitsfeatures wie das Zero-Trust-Prinzip führen zu intrinsischen Einbußen der Performance, weshalb das Testen der Performance ein wichtiger Teil des &lt;a href=&quot;https://www.cronn.de/referenzen/digitalisierung-gesundheitsamt&quot;&gt;Projektes&lt;/a&gt; war.&lt;/p&gt;

&lt;h3 id=&quot;auswahl-des-lasttesttools&quot;&gt;Auswahl des Lasttesttools&lt;/h3&gt;

&lt;p&gt;Wie so häufig muss man nicht alles selbst implementieren, daher haben wir uns nach einem Tool umgesehen, das Performance-Testing unterstützt. Da wir eine Webanwendung testen wollen, sollte es Browsertests ermöglichen. Zudem waren unsere Hauptanforderungen folgende:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Die Möglichkeit den Testcode in TypeScript zu schreiben, da wir TypeScript auch für das Frontend der Anwendung und die Ende-zu-Ende-Tests verwenden&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Open-Source-Verfügbarkeit des Tools&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Ausführbarkeit auf einem selbstgehosteten Server (keine reine Cloud-Lösung)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Ein gutes Reporting, um die Ergebnisse der Tests für uns und die Entwickler zu visualisieren.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nach der Evaluation mehrerer Tools haben wir uns für &lt;a href=&quot;https://grafana.com/docs/k6/latest/&quot;&gt;k6&lt;/a&gt; entschieden. k6 unterstützt Browsertests, ermöglicht die Entwicklung in TypeScript und bietet in Kombination mit Grafana sowie durch individuell definierbare Metriken ein umfassendes Reporting.&lt;/p&gt;

&lt;h3 id=&quot;unser-setup&quot;&gt;Unser Setup&lt;/h3&gt;

&lt;p&gt;k6 führt die Performance-Tests aus und erzeugt dabei bereits einige Metriken, wie z.B. &lt;a href=&quot;https://web.dev/articles/ttfb?hl=de#:~:text=Hinweis%3A%20%E2%80%9ETime%20to%20First%20Byte,um%20auf%20Anfragen%20zu%20reagieren.&quot;&gt;TTFB&lt;/a&gt; oder die Dauer der einzelnen Requests. Um diese und weitere Testergebnisse persistieren und visualisieren zu können, benötigten wir noch weitere Tools.&lt;/p&gt;

&lt;p&gt;Als Datenbank haben wir uns für &lt;a href=&quot;https://www.influxdata.com/&quot;&gt;InfluxDB&lt;/a&gt; entschieden, da diese dafür optimiert ist, Daten zeitaufgelöst zu speichern. Zur Visualisierung der Ergebnisse haben wir &lt;a href=&quot;https://grafana.com/oss/grafana&quot;&gt;Grafana-Dashboards&lt;/a&gt; genutzt, unter anderem da k6 zu Grafana gehört und es eine Schnittstelle zur InfluxDB bietet. Zur Abfrage der Daten aus der InfluxDB haben wir die proprietäre Datenbankabfragesprache &lt;a href=&quot;https://docs.influxdata.com/flux/v0/&quot;&gt;Flux&lt;/a&gt; genutzt. Diese wird jedoch vermutlich in der nächsten Major-Version v3 nicht mehr oder nur noch eingeschränkt unterstützt.&lt;/p&gt;

&lt;p&gt;Wir haben uns entschieden, die Tools lokal zu nutzen und sie in Docker-Container zu verpacken, um die Tests hardwareunabhängig ausführen zu können und nicht von Cloud-Anbietern abhängig zu sein. Alternativ besteht die Möglichkeit, &lt;a href=&quot;https://grafana.com/products/cloud/k6/&quot;&gt;Grafana Cloud k6&lt;/a&gt; zu verwenden, um die lokale Installation der Tools zu vermeiden.&lt;/p&gt;

&lt;h3 id=&quot;performance-tests-mit-k6&quot;&gt;Performance-Tests mit k6&lt;/h3&gt;

&lt;p&gt;Ein Test mit k6 lässt sich mit einem Javascript oder TypeScript-File ausführen (s. Beispielskript).&lt;/p&gt;
&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Scenario&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;k6/options&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryBrowserTest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@/modules/browser/schoolEntryBrowserTest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryApiTest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@/modules/api/schoolEntryApiTest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Scenario&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;schoolEntryBrowser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;schoolEntryBrowserTestFunction&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;constant-vus&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;vus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;15m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chromium&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;schoolEntryApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;schoolEntryApiTestFunction&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ramping-vus&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;startVUs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;discardResponseBodies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;systemTags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;setupTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;5m&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryBrowserTestFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryBrowserTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryApiTestFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;schoolEntryApiTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In diesem Skript werden Optionen für den Test sowie die auszuführenden Testfunktionen definiert. Die Optionen werden als JSON definiert. Eine wichtige Option, die den Testverlauf bestimmt, ist &lt;code class=&quot;highlighter-rouge&quot;&gt;scenarios&lt;/code&gt;. Dort können Szenarien definiert werden, die ausgeführt werden und somit den eigentlichen Test abbilden.&lt;/p&gt;

&lt;p&gt;Für ein solches Szenario wird eine auszuführende Funktion, sowie die Anzahl an ausführenden parallelen Nutzern, die in k6 Virtual User (VU) genannt werden, definiert. Mit der Angabe von Zeiträumen kann die Gesamtdauer des Szenarios bestimmt werden. Außerdem können Rampen definiert werden, um die Anzahl der parallelen User während des Tests zu erhöhen oder zu verringern. Eine andere Möglichkeit den Testverlauf zu beeinflussen, ist, ein Zeitintervall festzulegen, in dem eine konkrete Anzahl an VUs das Szenario durchlaufen sollen.&lt;/p&gt;

&lt;p&gt;Für einen Test können mehrere solcher Szenarien definiert werden, die mit unterschiedlichen Konfigurationen durchlaufen werden. Um diese Definition der Szenarien einfacher und schneller zu gestalten als ein langes JSON-File zu editieren, haben wir einen Builder entwickelt, der die Szenario-Konfiguration dynamisch erstellt und diesen auf GitHub zur Verfügung gestellt: &lt;a href=&quot;https://github.com/cronn/k6-scenario-builder&quot;&gt;https://github.com/cronn/k6-scenario-builder&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;unsere-erkenntnisse&quot;&gt;Unsere Erkenntnisse&lt;/h3&gt;

&lt;p&gt;Während des Testens sind uns einige Dinge aufgefallen, die es aus unserer Sicht zu berücksichtigen gilt. Zunächst ist es sinnvoll, eine dedizierte Maschine zur Verfügung zu haben, die die Tests ausführt. Da die Performance nicht nur durch Last vieler gleichzeitiger User beeinträchtigt wird, sondern auch von der Menge der Daten in der Datenbank, haben wir neben kurzen Spike-Tests auch Testszenarien erstellt, die eine Laufzeit über mehrere Stunden haben, um so die Datenmenge stetig zu erhöhen und eine Art Zeitraffer der tatsächlichen Nutzung der Anwendung zu simulieren. Diese Tests sind von einer externen Maschine deutlich komfortabler auszuführen als von dem eigenen Laptop.&lt;/p&gt;

&lt;p&gt;Zudem benötigt die Ausführung eines Tests ausreichend Ressourcen auf der ausführenden Maschine. Daher sollte darauf geachtet werden, dass während der Ausführung eines Tests stets noch freie Ressourcen vorhanden sind, um nicht die Ergebnisse ungewollt zu beeinflussen. Dies haben wir bei der Ausführung von Browsertests mit einigen VUs bemerkt. Eine zu große Anzahl an gleichzeitig geöffneten Browsern hat die auszuführende Maschine zum Bottleneck gemacht. Unsere Lösung dafür ist, neben Browsertests gleichzeitig Szenarien zu definieren, die eine möglichst gleiche User-Journey abbilden, jedoch die nötigen Requests direkt ans Backend schicken, um somit die Last aufs Backend browserunabhängig zu erhöhen. Solche API-Szenarien eignen sich auch gut, um schnell ein Szenario zusammenzubauen und somit browserunabhängig einen Überblick über die Performance des Backends zu bekommen.&lt;/p&gt;

&lt;p&gt;Eine weitere Erkenntnis von uns war, auf einer möglichst produktionsnahen Umgebung zu testen. Denn auch die Konfiguration einer Umgebung, gerade ein komplexer Microservice-Cluster, kann die Performance erheblich beeinflussen. Neben dem Ausführen der Tests von einer anderen Maschine und dem Testen auf einer produktionsähnlichen Umgebung war es für uns dennoch wichtig, auch das Testen vollständig auf dem eigenen Laptop zu ermöglichen. Dies ermöglicht die unabhängige Entwicklung neuer Szenarien durch die Entwickler und einen einfachen Zugang zu Datenbanken und Logs.&lt;/p&gt;

&lt;p&gt;Es ist vorgekommen, dass wir durch die Konfiguration unserer Szenarios, vor allem bei langen Tests, fachliche Limits überschritten haben. Zum Beispiel haben wir unrealistisch viele Termine für einen Tag oder User angelegt, oder sogar zu viele User mit den gleichen Berechtigungen gehabt. Viele Größen können die Performance beeinflussen und sollten deshalb möglichst frühzeitig abgesteckt werden. Dadurch können wenig aussagekräftige Testläufe vermieden werden. Trotzdem war es uns auch wichtig, die bekannten Limits bewusst zu überschreiten, um die Reaktion der Anwendung zu testen und dort dann gegebenenfalls nachzubessern. Denn es ist ja nicht gesagt, dass der Kunde seine fachlichen Limits kennt oder diese durch technische Fehler nicht überschritten werden. Bei einem Termin zu viel sollte die Anwendung nicht unbedienbar werden. Ein Learning war für uns daher, fachliche Limits früh abzuklären und in den Tests zu beachten.&lt;/p&gt;

&lt;h3 id=&quot;vor--und-nachteile-von-k6&quot;&gt;Vor- und Nachteile von k6&lt;/h3&gt;

&lt;p&gt;Während des Testens mit k6 sind wir immer mal wieder auf Probleme gestoßen. Eine erhebliche Einschränkung beim Entwickeln von Performance-Tests mit k6 ist ein fehlender Debugger. k6 nutzt eine eigene &lt;a href=&quot;https://github.com/grafana/sobek&quot;&gt;JavaScript-Engine&lt;/a&gt;, um den Testcode auszuführen, für die es keinen Debugger gibt. Die Javascript-Engine hat auch weitere Schwächen, denen man sich bewusst sein sollte. Beispielsweise unterstützt sie die verbreitete Fetch API nicht. Im Zusammenhang mit Browsertests sind Schwächen von k6, dass Methoden wie &lt;em&gt;goto()&lt;/em&gt;, die darauf warten sollen, dass eine Seite geladen ist, im Zusammenspiel mit Chromium nicht immer zuverlässig funktionieren, was hin und wieder zu Timing-Problemen führt. Darüber hinaus müssen Locator über XPaths identifiziert werden, was sehr regressionsanfällig ist, sowie häufig unschön und lang. Zuletzt ist auch die Dokumentation von k6 häufig relativ knapp.&lt;/p&gt;

&lt;p&gt;Einige andere Dinge haben sich als Vorteile von k6 herausgestellt. Das Reporting im Zusammenspiel mit der InfluxDB und Grafana hat wie erhofft sehr gut funktioniert. Über dieses Setup lassen sich ohne große Vorkenntnisse schnell aussagekräftige Plots erstellen und in einem Dashboard anzeigen, sodass die Testergebnisse analysiert und kommuniziert werden können. Außerdem funktioniert das parallele Ausführen von verschiedenen Szenarien, die jeweils ebenfalls mit parallelen virtuellen Usern ausgeführt werden, sehr gut. Dadurch lassen sich komplexe Szenarien erstellen, die verschiedene Arten von Performance-Tests wie Load-Tests, Spike-Tests und Soak-Tests abbilden. Dass die Testoptionen und insbesondere die Szenarien als JSON beschrieben werden ist sehr angenehm, da es einen fließenden Übergang zum Typescript-Code bietet. Außerdem hat man die Möglichkeit, die Browsertests in einem Headful Mode laufen zu lassen, sodass sich Probleme während der Ausführung erkennen lassen und behoben werden können.&lt;/p&gt;

&lt;h3 id=&quot;zusammenfassung&quot;&gt;Zusammenfassung&lt;/h3&gt;

&lt;p&gt;Da wir während der Testphase unsere Tests und unser Setup stetig weiterentwickelt haben, hat sich für uns ein iterativer Ansatz ausgezahlt. Wir sind mit zwei einfachen Szenarien für Module gestartet, die zu den wichtigsten in der Anwendung gehören. Bei diesen ersten Szenarien haben wir festgestellt, dass wir weitere Metriken und Plots in unseren Reports benötigen, um die Ergebnisse analysieren zu können. Iterativ haben wir dann Metriken zu unseren Tests hinzugefügt und im Grafana-Board visualisiert. Dies waren Informationen wie die Dauer von Requests, die Ladezeiten von bestimmten Seiten oder auch die CPU- und RAM-Auslastung der ausführenden Maschine. Für uns war vor allem die Dauer einzelner Requests von Bedeutung, welche Informationen relevant sind, hängt jedoch von der Anwendung ab. Durch in k6 eingebaute Metrik-Typen lässt sich die Erhebung von Informationen flexibel gestalten.&lt;/p&gt;

&lt;p&gt;Die Arbeit mit k6 hat uns sowohl Stärken als auch Schwächen des Tools gezeigt. Ob k6 passend ist, hängt sicher vom Anwendungsfall ab, für uns war es aber trotz einiger signifikanter Schwächen ein passendes Tool.&lt;/p&gt;</content><author><name>simonBiwer</name></author><summary type="html">Wir teilen unsere Erfahrungen mit Performance-Testing im Projekt GA-Lotse – mit einem Setup aus k6, Grafana, InfluxDB und TypeScript.</summary></entry><entry xml:lang="en"><title type="html">Analyzing Business Reports with LLMs – Part 2</title><link href="https://blog.cronn.de/en/ai/largelanguagemodels/2025/06/24/analyse-von-geschaeftsberichten-mit-llms-2-en.html" rel="alternate" type="text/html" title="Analyzing Business Reports with LLMs – Part 2" /><published>2025-06-24T00:00:00-05:00</published><updated>2025-06-24T00:00:00-05:00</updated><id>https://blog.cronn.de/en/ai/largelanguagemodels/2025/06/24/analyse-von-geschaeftsberichten-mit-llms-2-en</id><content type="html" xml:base="https://blog.cronn.de/en/ai/largelanguagemodels/2025/06/24/analyse-von-geschaeftsberichten-mit-llms-2-en.html">&lt;p&gt;Welcome back to our series on analysing annual reports with AI. In &lt;a href=&quot;https://blog.cronn.de/en/ai/largelanguagemodels/2023/07/26/analyzing-business-reports-with-chatgpt-part1.html&quot;&gt;Part One&lt;/a&gt; we showed how the extraction of key figures from annual reports with LLMs (such as ChatGPT) works. Now we are going deeper and showing the final working solution, which we are using in cooperation with North Data.&lt;/p&gt;

&lt;p&gt;We have already demonstrated how relevant information can be filtered out of the dense text of annual reports in a structured way. But if you want to scale this process in practice, you quickly reach its limits – be it in terms of accuracy across many different documents, the robust processing of complex layouts and tables, or the cost-effectiveness of large-scale analysis.&lt;/p&gt;

&lt;p&gt;This is exactly where there have been many exciting developments. With &lt;strong&gt;Gemini Flash&lt;/strong&gt; from Google, a model is available which reshuffles the cards for automated document analysis in terms of speed, contextual understanding, and the delivery of structured data.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; In this second part, we will ask: what makes Gemini Flash so more powerful for this specific task than previous approaches or the classic OCR pipelines? How does it make the step from feasibility study to productive tool? Let us look under the hood.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/Analyse-von-Geschaeftsberichten-2-northdata-grafik.webp&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;On the left side: Unstructured sample documents; in the centre an arrow pointing to the right, labelled ‘AI’; the arrow points to JSON code.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; Gemini extracts structured JSON code from PDFs. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;the-classic-approach-ocr-as-the-basis-but-not-the-whole-solution&quot;&gt;The classic approach: OCR as the basis, but not the whole solution&lt;/h3&gt;
&lt;p&gt;Before we dive into Gemini’s capabilities, it is worth looking at the traditional way of extracting data from PDFs. This most commonly starts with &lt;strong&gt;Optical Character Recognition (OCR)&lt;/strong&gt;. OCR tools generate text from scanned documents or image-only PDFs by converting pixels into letters. The result is not only the raw text content, but often also its position on the page, usually in the form of coordinates or so-called bounding boxes for each recognized word or line.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/Analyse-von-Geschaeftsberichten-2-table.webp&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;In an example table (‘Balance sheet’), terms and figures are marked with bounding boxes.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; OCR Bounding Boxes from Azure Document Intelligence. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;However, for a meaningful analysis we need &lt;em&gt;structured&lt;/em&gt; data, not continuous text. This is where the challenges begin.&lt;/p&gt;

&lt;p&gt;The first hurdle lays in the structure in the pure text output being recognized. How do you automatically identify tables, related key-value pairs (such as “revenue: €10 million”) or semantically meaningful blocks? This often requires complex, downstream steps – whether purpose-built parsers, rule-based systems that look for specific patterns, or even separate machine learning models trained on tasks such as table recognition.&lt;/p&gt;

&lt;p&gt;However, these downstream systems are often &lt;strong&gt;susceptible to layout changes&lt;/strong&gt;. Small adjustments in the design of a report from one year to the next or the format differing between companies can throw off painstakingly created rules or parsers and make them unusable.&lt;/p&gt;

&lt;p&gt;In addition, there is a lack of &lt;strong&gt;contextual understanding&lt;/strong&gt;. OCR provides the text but does not understand its meaning. Recognizing that the term “Total Assets” on page 10 refers to the same metric as a detailed breakdown in a table on page 45 is beyond the capabilities of pure text recognition.&lt;/p&gt;

&lt;p&gt;All these factors create complexity and thus lead to a &lt;strong&gt;high development and maintenance effort&lt;/strong&gt;. It can be said that OCR is a valuable tool, but for the &lt;strong&gt;extraction of &lt;em&gt;structured&lt;/em&gt; data&lt;/strong&gt; it is usually only the first step in a complex and often fragile processing chain.&lt;/p&gt;

&lt;h3 id=&quot;our-path-to-productive-use-evaluation-model-selection-and-integration&quot;&gt;Our path to productive use: evaluation, model selection and integration&lt;/h3&gt;

&lt;p&gt;The leap from successful demonstration (as shown in Part 1&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;) to a reliable, scalable production system required a systematic approach and further developments in several areas.&lt;/p&gt;

&lt;p&gt;Firstly, a &lt;strong&gt;solid evaluation&lt;/strong&gt; was essential. To this end we manually curated a dataset of 100 representative English annual reports. For the most important key figures, the correct values (ground truth) were annotated by hand and collected in a table. Only with such a reliable basis can the quality of different models and approaches be objectively measured and tracked over time.&lt;/p&gt;

&lt;p&gt;Secondly, we significantly expanded the scope of extraction. Instead of just a few key figures, the goal was now to reliably extract a wide range of over 20 relevant values per report. This includes, among other things, the wage costs, information on profit and loss, cash flow, but also data such as the average number of employees or the name of the auditor.&lt;/p&gt;

&lt;p&gt;These more demanding goals led us to test different models. In the end, the choice fell on &lt;strong&gt;Gemini 2.0 Flash Lite&lt;/strong&gt;: This model optimally combined all the decisive factors for our application.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/Analyse-von-Geschäftsberichten-2-graph.webp&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Graph, Y-axis: Artificial Analysis Intelligence Index, 0 to 75; X-axis: Price (USD per M tokens), 0 - 8 USD; the graph is divided into four quadrants, Gemini alone is in the upper left quadrant (score 70.49, 3.44 USD); all other models are significantly more expensive or perform worse in the Intelligence Score.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; LLM comparison based on the parameters &quot;intelligence&quot; and &quot;price&quot;, via  &lt;a href=&quot;https://artificialanalysis.ai/models?models=llama-4-maverick%2Cllama-4-scout%2Cgemini-2-0-flash-lite-001%2Cgemini-2-5-pro%2Cclaude-3-5-haiku%2Cclaude-3-7-sonnet-thinking%2Cpixtral-large-2411%2Cgrok-3%2Cgpt-4o-chatgpt-03-25%2Cgemini-1-5-pro#intelligence-vs-price&quot; target=&quot;_blank&quot;&gt;artificialanalysis.ai&lt;/a&gt;. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Quality &amp;amp; Speed:&lt;/strong&gt; In our tests, Gemini 2.0 Flash Lite showed high accuracy for most of the targeted metrics, often keeping up with that of larger, more expensive models. Google itself positions the Flash models as optimized for tasks where it is important to maintain high speed and efficiency while maintaining high quality &lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. Our experience confirms that the model lives up to its “flash” in its name in terms of processing speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; A decisive factor for large-scale deployment is cost. Gemini 2.0 Flash Lite is significantly cheaper than the larger Pro models. Compared to older models like gpt-3.5-turbo-16k, which still cost about $3 per million input tokens in July 2023 &lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;, the Gemini Flash variant we used is cheaper by a factor of 40 &lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;! This makes the processing of thousands of reports economically viable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodality &amp;amp; Context:&lt;/strong&gt; A significant advantage over plain text models or classic OCR pipelines is Gemini’s multimodality. Put simply, instead of just delivering the raw text and its coordinates (like traditional OCR), Gemini Flash can “read” the text and “see” the page layout at the same time. It “understands” how text is arranged in columns or tables, recognizes headings, and can interpret images or charts in the document. As a result, it is better at capturing context which the pure text order often does not convey. This is a great advantage, especially with the complex and varied layouts of annual reports. Coupled with the long context window, which allows the analysis of large document sections in one go, this is a decisive step forward.&lt;/p&gt;

&lt;p&gt;This combination of good quality, high speed, low cost, and the ability to understand documents holistically made Gemini 2.0 Flash Lite a viable choice for our productive deployment in collaboration with North Data.&lt;/p&gt;

&lt;h3 id=&quot;gemini-flash-in-action-the-workflow-with-structured-outputs&quot;&gt;Gemini Flash in Action: The Workflow with Structured Outputs&lt;/h3&gt;

&lt;p&gt;The core of our approach combines the strengths of Gemini with pragmatic solutions to deal with the peculiarities of large documents.&lt;/p&gt;

&lt;p&gt;A central problem with annual reports is that they often comprise hundreds of pages. While handing over the entire document to Gemini would be ideal for context, it is too expensive for mass use. To get around this problem, we have developed a multi-step approach: First, we still rely on proven &lt;strong&gt;OCR technology&lt;/strong&gt; to extract the plain text of the entire document. This raw text then serves as the basis for a quick &lt;strong&gt;preliminary analysis&lt;/strong&gt; using keywords. We look for terms and phrases that typically indicate relevant sections, such as “Consolidated Balance Sheet”, “Income Statement” or “Notes to the Financial Statements”.&lt;/p&gt;

&lt;p&gt;Based on this analysis we then select the &lt;strong&gt;up to 100 pages&lt;/strong&gt; that are most likely to contain the financial ratios we are looking for. &lt;em&gt;Only this selection&lt;/em&gt; is then passed on to Gemini Flash Lite as a PDF context. This trick not only significantly reduces processing costs but also helps to focus the model on the important parts of the document and minimize the “noise” of irrelevant pages.&lt;/p&gt;

&lt;p&gt;After isolating the relevant pages, we commission Gemini to extract them into a predefined format. Another building block for precise results is the use of so-called &lt;strong&gt;structured outputs&lt;/strong&gt;. Gemini can not only generate text but also provides directly structured JSON data which follows a predetermined scheme.&lt;/p&gt;

&lt;p&gt;To do this, we define a clear target scheme in advance, which in turn defines exactly which data fields we expect and in which format (such as “number”, “text”, “currency symbol”). In Python, we like to use Pydantic for easy definition and validation. We explicitly give this structure to the model as an instruction. This is not only practical for automated further processing, but also demonstrably improves quality: In our tests, this step alone led to an &lt;strong&gt;improvement in the evaluation result of around 4%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a simplified Python example to illustrate the principle with the &lt;code class=&quot;highlighter-rouge&quot;&gt;google-genai&lt;/code&gt; library and structured outputs:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;google&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;genai&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;google.genai&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;types&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pydantic&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BaseModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;genai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GEMINI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# Define the desired output structure using Pydantic
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FinancialData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BaseModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;revenue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Total revenue reported for the fiscal year.&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;net_income&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Net income or profit after tax.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_assets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Total assets value.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fiscal_year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The ending year of the fiscal period.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;currency_symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Currency symbol used for major values (e.g., $, £, €).&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# Upload the relevant PDF pages (assuming 'selected_report_pages.pdf' was created by pre-filtering)
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;'selected_report_pages.pdf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
Please analyze the provided pages from the annual report PDF.
Extract the following financial figures for the main consolidated entity reported:
- Total Revenue
- Net Income (Profit after tax)
- Total Assets
- The Fiscal Year End
- The primary Currency Symbol used for the main financial figures (£, $, € etc.)

Return the data strictly adhering to the provided 'FinancialData' schema.
If a value cannot be found or determined confidently, leave the corresponding field null.
Pay close attention to units (e.g., thousands, millions).
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;generate_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gemini-2.0-flash-lite-001&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdf_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GenerateContentConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;response_mime_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;response_schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FinancialData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;extracted_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FinancialData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_validate_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extracted_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;An error occurred: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;a-look-at-the-numbers-how-well-does-it-really-work&quot;&gt;A look at the numbers: How well does it really work?&lt;/h3&gt;

&lt;p&gt;To objectively assess the actual performance of our approach with Gemini Flash, we created a dataset of 100 manually annotated business reports. This serves as ground truth against which we check the extraction results of the model.&lt;/p&gt;

&lt;p&gt;The overall accuracy across all metrics and reports for our approach was &lt;strong&gt;83.5%&lt;/strong&gt;. These were the first feasibility values for the solution we integrated at North Data. This is a solid basis which demonstrates that the approach works. However, it gets more interesting when you look at the accuracy for individual metrics:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Key figure (parameters)&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;83.5%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;capital&lt;/td&gt;
      &lt;td&gt;96.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cash&lt;/td&gt;
      &lt;td&gt;95.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;employees&lt;/td&gt;
      &lt;td&gt;95.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;revenue&lt;/td&gt;
      &lt;td&gt;95.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;equity&lt;/td&gt;
      &lt;td&gt;98.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;currencySymbol&lt;/td&gt;
      &lt;td&gt;99.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;auditorName&lt;/td&gt;
      &lt;td&gt;89.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;materials&lt;/td&gt;
      &lt;td&gt;89.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;…&lt;/td&gt;
      &lt;td&gt;…&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;liabilities (creditors)&lt;/td&gt;
      &lt;td&gt;75.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;currentAssets&lt;/td&gt;
      &lt;td&gt;64.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;realEstate&lt;/td&gt;
      &lt;td&gt;60.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;receivables&lt;/td&gt;
      &lt;td&gt;52.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;tax&lt;/td&gt;
      &lt;td&gt;41.0%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;what-does-this-table-tell-us-and-what-are-the-current-hurdles&quot;&gt;What does this table tell us and what are the current hurdles?&lt;/h3&gt;

&lt;p&gt;The results paint a clear picture: The model achieves remarkably high accuracy values for &lt;strong&gt;clearly defined master data or values&lt;/strong&gt;, which are often prominently and relatively uniformly shown in annual reports. These include, for example, &lt;code class=&quot;highlighter-rouge&quot;&gt;capital&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;equity&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;employees&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;cash&lt;/code&gt; or the &lt;code class=&quot;highlighter-rouge&quot;&gt;currency symbol&lt;/code&gt;. Fortunately, &lt;strong&gt;hallucinations&lt;/strong&gt; – for example inventing numbers that do not exist in the document – were not a significant problem in our tests. If errors occurred, it was usually due to misinterpretations of existing figures and not to their free invention.&lt;/p&gt;

&lt;p&gt;It becomes more difficult for the model with more complex key figures. This is where the limitations of the current approach become apparent, especially when it comes to &lt;strong&gt;semantic fuzziness&lt;/strong&gt; and varying levels of detail. Many balance sheet items can be defined, named, or broken down differently in reports. Terms such as “total assets” are not always clear – does it mean the balance sheet total before or after deduction of certain items such as goodwill, for example the intangible value?&lt;/p&gt;

&lt;p&gt;The exact definition of &lt;code class=&quot;highlighter-rouge&quot;&gt;current assets&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;receivables&lt;/code&gt; or liabilities varies between companies and reporting standards. This is where the model sometimes reaches its limits in deducing the exact definition valid in the respective report from the immediate context alone.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;dependence on layouts&lt;/strong&gt; and the placement of information also plays a role. Some assets, such as &lt;code class=&quot;highlighter-rouge&quot;&gt;realEstate&lt;/code&gt; (real estate assets), are often not prominently found on the main pages of the balance sheet but are hidden in detail in the “Notes to the Financial Statements” (Appendix). The model’s ability to correctly map such information across different pages and layouts is heavily challenged and results in lower accuracy scores.&lt;/p&gt;

&lt;p&gt;Finally, some metrics require &lt;strong&gt;more complex interpretations or implicit calculations&lt;/strong&gt;. The extraction of values such as &lt;code class=&quot;highlighter-rouge&quot;&gt;tax &lt;/code&gt; is a good example of this. Different types of taxes (income taxes, sales taxes, etc.) and deferred taxes can often be spread over several sections. The correct aggregation and interpretation of this information is challenging, which explains the current accuracy of only 41% for this metric.&lt;/p&gt;

&lt;p&gt;These quantitative results confirm our qualitative observations: the model is excellent at finding clearly labelled information. However, it reaches its limits when dealing with issues such as ambiguities in wording, widely varying or complex layouts, and the need to understand implicit knowledge or contexts across multiple text passages.&lt;/p&gt;

&lt;p&gt;Another important aspect is the &lt;strong&gt;varying accuracy between different companies&lt;/strong&gt;. The standard deviation of accuracy per company is about 9.2%. It is particularly striking that the accuracy of the large, individually designed reports from listed companies (PLCs) such as AstraZeneca (50%), Barclays (65%), HSBC (50%), Shell (70%) or Unilever (55%) tends to be significantly lower than average. Tests with excerpts of different lengths showed that the length of the context to be mastered is not a major difficulty for Gemini, we therefore assume that the uniqueness of the reporting structures of these groups is particularly challenging for the model. While Gemini Flash Lite handles layouts that are often created by smaller companies using off-the-shelf software, these complex cases are a bigger hurdle. One explanation could be that the reports that deviate from the standard rarely made it into Gemini’s training data.&lt;/p&gt;

&lt;p&gt;Another recurring problem is the correct capture of &lt;strong&gt;units and scales&lt;/strong&gt;. Missing or misinterpreting information such as “in thousands of £” or “millions of USD” will result in extracted values that are wrong by factors of 1,000 or 1,000,000. Here, robust downstream validation rules and targeted prompting are necessary to sensitize the model to these details.&lt;/p&gt;

&lt;p&gt;The representation of &lt;strong&gt;negative numbers&lt;/strong&gt;, which is often done by parentheses in annual reports (e.g. “(1.234)” instead of “-1.234”), also requires an explicit note in the prompt so that the model interprets this convention correctly and extracts the numbers with the correct sign. As already mentioned, hallucinations do not pose any major problems here (as it was with older models), it is the interpretation of the numbers that does not always succeed.&lt;/p&gt;

&lt;p&gt;Finally, we are also faced with the classic trade-off between costs and performance in particularly complex cases. More sophisticated reasoning approaches such as Chain-of-Thought (CoT), in which the model makes its “thought steps” explicit, or the use of even larger and more powerful models (for example Gemini 2.5 Pro) could remedy the problems mentioned, especially when analysing the more complex reports.&lt;/p&gt;

&lt;p&gt;However, these are currently often much more expensive. For example, &lt;strong&gt;Gemini 2.5 Pro is currently 16 to 32 times more expensive than the Gemini 2.0 Flash Lite we used&lt;/strong&gt;. The common GPT-4.1, which is used in ChatGPT, also costs $2 per 1 million input tokens – about 27 times as much as Gemini 2.0 Flash Lite. Using our solution to process an average report from our 30-page test dataset costs only about $0.0007!&lt;/p&gt;

&lt;h3 id=&quot;conclusion-gemini-flash-as-a-powerful-addition-to-the-toolbox&quot;&gt;Conclusion: Gemini Flash as a powerful addition to the toolbox&lt;/h3&gt;

&lt;p&gt;Gemini Flash has proven to be a useful building block for us to take the extraction of structured data from annual reports to a new level and bring it into productive use at North Data. It does not necessarily replace the entire classic pipeline (as our OCR pre-filtering shows), but it does provide a powerful, integrated alternative to the core process of intelligent data extraction and structuring.&lt;/p&gt;

&lt;p&gt;The ability to understand layouts, work within a larger context, and deliver structured outputs significantly reduces complexity and maintenance compared to traditional, multi-tiered approaches. The challenges remain, but the progress is clear and opens new opportunities for automated financial data analysis.&lt;/p&gt;

&lt;p&gt;We are excited to see how this technology will develop further and what new solutions will emerge. Have you had similar experiences or developed different strategies? Share your thoughts with us!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog post was written with the support of Gemini 2.5 Pro.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://getomni.ai/ocr-benchmark&quot; target=&quot;_blank&quot;&gt;OmniAI OCR Benchmark&lt;/a&gt;, retrieved 17/06/25 &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://blog.cronn.de/en/ai/largelanguagemodels/2023/07/26/analyzing-business-reports-with-chatgpt-part1.html&quot; target=&quot;_blank&quot;&gt;cronn Blog: Analyzing Business Reports with ChatGPT – Part I&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-0-flash-lite?hl=de&quot; target=&quot;_blank&quot;&gt;Documentation Google Gemini 2.0 Flash-Lite&lt;/a&gt;, retrieved 17/06/25 &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/20230614104237/https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;Web Archive: OpenAI-Preise vom 14. Juni 2023&lt;/a&gt;, retrieved 17/06/25 &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://ai.google.dev/gemini-api/docs/pricing?hl=de&quot; target=&quot;_blank&quot;&gt;Prices for Gemini Developer API&lt;/a&gt;, retrieved 17/06/25 &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>leonardThiele</name></author><summary type="html">An AI use case in action: the extraction of key figures from annual reports using LLM.</summary></entry><entry xml:lang="de"><title type="html">Analyse von Geschäftsberichten mit LLMs – Teil 2</title><link href="https://blog.cronn.de/de/ai/largelanguagemodels/2025/06/24/analyse-von-geschaeftsberichten-mit-llms-2.html" rel="alternate" type="text/html" title="Analyse von Geschäftsberichten mit LLMs – Teil 2" /><published>2025-06-24T00:00:00-05:00</published><updated>2025-06-24T00:00:00-05:00</updated><id>https://blog.cronn.de/de/ai/largelanguagemodels/2025/06/24/analyse-von-geschaeftsberichten-mit-llms-2</id><content type="html" xml:base="https://blog.cronn.de/de/ai/largelanguagemodels/2025/06/24/analyse-von-geschaeftsberichten-mit-llms-2.html">&lt;p&gt;Willkommen zurück zu unserer Serie über die Analyse von Geschäftsberichten mit KI! Im &lt;a href=&quot;https://blog.cronn.de/de/ai/largelanguagemodels/2023/07/26/analyse-von-geschaeftsberichten-mit-chatgpt-1.html&quot; target=&quot;_blank&quot;&gt;ersten Teil&lt;/a&gt; haben wir anhand eines Beispiels gezeigt, wie die Extraktion von Kennzahlen aus Geschäftsberichten mit LLMs wie ChatGPT grundsätzlich funktioniert. Jetzt gehen wir weiter in die Tiefe und zeigen dafür eine Lösung, die wir in Zusammenarbeit mit North Data produktiv einsetzen.&lt;/p&gt;

&lt;p&gt;Wir konnten damals demonstrieren, wie sich relevante Informationen aus den dichten Textwüsten von Geschäftsberichten strukturiert herausfiltern lassen. Doch wer das in der Praxis skalieren will, stößt schnell an Grenzen – sei es bei der Genauigkeit über viele verschiedene Dokumente hinweg, der robusten Verarbeitung komplexer Layouts und Tabellen oder der Wirtschaftlichkeit, die für eine großflächige Analyse nötig ist.&lt;/p&gt;

&lt;p&gt;Genau hier hat sich in der Zwischenzeit aber einiges getan. Mit &lt;strong&gt;Gemini Flash&lt;/strong&gt; von Google steht ein Modell bereit, das die Karten für die automatisierte Dokumentenanalyse in Sachen Geschwindigkeit, Kontextverständnis und dem Ausliefern strukturierter Daten neu mischt.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; In diesem zweiten Teil wollen wir daher tief eintauchen: Was macht Gemini Flash so viel leistungsfähiger für diese spezifische Aufgabe als frühere Ansätze oder die klassischen OCR-Pipelines? Wie ermöglicht es den Schritt von der Machbarkeitsstudie zum produktiven Werkzeug? Werfen wir einen Blick unter die Haube.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/Analyse-von-Geschaeftsberichten-2-northdata-grafik.webp&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Auf der Linken Seite: Unstrukturierte Beispieldokumente; Mitte ein Pfeil der nach rechts Zeigt, Aufschrift „AI“; der Pfeil Zeigt auf JSON-Code.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; Gemini extrahiert strukturierten JSON-Code aus PDFs. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;der-klassische-ansatz-ocr-als-basis-aber-nicht-die-ganze-lösung&quot;&gt;Der klassische Ansatz: OCR als Basis, aber nicht die ganze Lösung&lt;/h3&gt;

&lt;p&gt;Bevor wir uns den Fähigkeiten von Gemini widmen, lohnt sich ein kurzer Blick auf den traditionellen Weg zur Datenextraktion aus PDFs. Dieser beginnt fast immer mit &lt;strong&gt;Optical Character Recognition (OCR)&lt;/strong&gt;. OCR-Tools helfen uns, wenn es darum geht, Text aus gescannten Dokumenten oder reinen Bild-PDFs lesbar zu machen. Sie wandeln Pixel in Buchstaben um. Das Ergebnis ist nicht nur der „rohe“ Textinhalt, sondern oft auch dessen Position auf der Seite, meist in Form von Koordinaten oder sogenannten Bounding Boxes für jedes erkannte Wort oder jede Zeile.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/Analyse-von-Geschaeftsberichten-2-table.webp&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;In einer Beispieltabelle („Balance sheet“) sind Begriffe und Zahlen durch Bounding Boxes markiert.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; Bounding Boxes bei OCR durch Azure Document Intelligence. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Nach diesem rohen Text samt Koordinaten fängt die eigentliche Arbeit oft erst an, denn für eine sinnvolle Analyse brauchen wir &lt;em&gt;strukturierte&lt;/em&gt; Daten, keinen Fließtext. Hier beginnen die Herausforderungen:&lt;/p&gt;

&lt;p&gt;Zuerst muss die Struktur im reinen Text-Output erkannt werden. Wie identifiziert man automatisch Tabellen, zusammengehörige Key-Value-Paare (wie „Umsatz: 10 Mio. €“) oder semantisch sinnvolle Blöcke? Dafür sind häufig komplexe, nachgelagerte Schritte notwendig – seien es speziell entwickelte Parser, regelbasierte Systeme, die auf bestimmte Muster achten, oder sogar separate Machine-Learning-Modelle, die auf Aufgaben wie Tabellenerkennung trainiert wurden.&lt;/p&gt;

&lt;p&gt;Diese nachgelagerten Systeme sind allerdings oft &lt;strong&gt;anfällig für Layout-Änderungen&lt;/strong&gt;. Kleine Anpassungen im Design eines Berichts von einem Jahr zum nächsten, oder die unterschiedlichen Formate verschiedener Unternehmen, können mühsam erstellte Regeln oder Parser aus dem Tritt bringen und unbrauchbar machen.&lt;/p&gt;

&lt;p&gt;Hinzu kommt das fehlende &lt;strong&gt;Kontextverständnis&lt;/strong&gt;. OCR liefert zwar den Text, versteht aber dessen Bedeutung nicht. Zu erkennen, dass sich der Begriff „Total Assets“ auf Seite 10 auf dieselbe Kennzahl bezieht wie eine detaillierte Aufschlüsselung in einer Tabelle auf Seite 45, übersteigt die Fähigkeiten reiner Texterkennung.&lt;/p&gt;

&lt;p&gt;All diese Faktoren führen zu Komplexität und somit zu einem hohen &lt;strong&gt;Entwicklungs- und Wartungsaufwand&lt;/strong&gt;. Es lässt sich feststellen: OCR ist ein wichtiges Werkzeug im Kasten. Aber für das Ziel der &lt;strong&gt;End-to-End-Extraktion &lt;em&gt;strukturierter&lt;/em&gt; Daten&lt;/strong&gt; ist es meist nur der erste Schritt in einer komplexen und oft fragilen Verarbeitungskette.&lt;/p&gt;

&lt;h3 id=&quot;unser-weg-zum-produktiveinsatz-evaluation-modellwahl-und-integration&quot;&gt;Unser Weg zum Produktiveinsatz: Evaluation, Modellwahl und Integration&lt;/h3&gt;

&lt;p&gt;Der Sprung von einer erfolgreichen Demonstration (wie in Teil 1 gezeigt&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;) zu einem zuverlässigen, skalierbaren Produktivsystem erforderte einen systematischen Ansatz und Weiterentwicklungen in mehreren Bereichen.&lt;/p&gt;

&lt;p&gt;Zunächst war eine &lt;strong&gt;solide Evaluation&lt;/strong&gt; unerlässlich. Wir haben also manuell einen Datensatz aus 100 repräsentativen englischen Geschäftsberichten kuratiert. Für die wichtigsten Kennzahlen wurden die korrekten Werte (Ground Truth) von Hand annotiert und in einer Tabelle gesammelt. Nur mit einer solchen verlässlichen Basis lässt sich die Qualität verschiedener Modelle und Ansätze objektiv messen und über die Zeit verfolgen.&lt;/p&gt;

&lt;p&gt;Parallel dazu erweiterten wir den Umfang der Extraktion im Vergleich zur alten Lösung deutlich. Statt nur einiger weniger Kennzahlen war das Ziel nun, eine breite Palette von über 20 relevanten Werten pro Bericht zuverlässig zu extrahieren. Dazu gehören unter anderem die vom Unternehmen ausgewiesenen Lohnkosten, Angaben zu Gewinn und Verlust, Barmittel, aber auch Daten wie die durchschnittliche Mitarbeiterzahl oder der Name des Wirtschaftsprüfers.&lt;/p&gt;

&lt;p&gt;Diese anspruchsvolleren Ziele führten uns zu Tests verschiedener Modelle. Die Wahl fiel schließlich auf &lt;strong&gt;Gemini 2.0 Flash Lite:&lt;/strong&gt; Dieses Modell vereinte für unseren Anwendungsfall alle entscheidenden Faktoren optimal.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/Analyse-von-Geschäftsberichten-2-graph.webp&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Graf, Y-Achse: Artificial Analysis Intelligence Index, 0 bis 75; X-Achse: Price (USD per M Tokens), 0 - 8 USD; der Graf ist in vier Quadranten unterteilt, Gemini liegt alleine im oberen linken Quadranten (Score 70.49, 3.44 USD); alle anderen Modelle sind deutlich teurer oder schneiden im Intelligence Score schlechter ab.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; LLM-Vergleich anhand der Parameter „Intelligenz“ und „Preis“, via &lt;a href=&quot;https://artificialanalysis.ai/models?models=llama-4-maverick%2Cllama-4-scout%2Cgemini-2-0-flash-lite-001%2Cgemini-2-5-pro%2Cclaude-3-5-haiku%2Cclaude-3-7-sonnet-thinking%2Cpixtral-large-2411%2Cgrok-3%2Cgpt-4o-chatgpt-03-25%2Cgemini-1-5-pro#intelligence-vs-price&quot; target=&quot;_blank&quot;&gt;artificialanalysis.ai&lt;/a&gt;. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Qualität &amp;amp; Geschwindigkeit:&lt;/strong&gt; In unseren Tests zeigte Gemini 2.0 Flash Lite eine überraschend hohe Genauigkeit für die meisten der anvisierten Kennzahlen, die oft mit der von größeren, teureren Modellen mithalten konnte. Google selbst positioniert die Flash-Modelle als optimiert für Aufgaben, bei denen es auf hohe Geschwindigkeit und Effizienz bei gleichzeitig guter Qualität ankommt&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. Unsere Erfahrungen bestätigen, dass das Modell seinem „Flash“ im Namen in puncto Verarbeitungsgeschwindigkeit gerecht wird.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kosten:&lt;/strong&gt; Ein entscheidender Faktor für den Einsatz im großen Maßstab sind die Kosten. Gemini 2.0 Flash Lite ist deutlich günstiger als die größeren Pro-Modelle. Im Vergleich zu älteren Modellen wie gpt-3.5-turbo-16k aus dem ersten Teil, das im Juli 2023 noch etwa 3 US-Dollar pro Million Input-Token kostete&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;, ist die von uns genutzte Gemini-Flash-Variante um den Faktor 40 günstiger&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;! Das macht die Verarbeitung tausender Berichte wirtschaftlich tragbar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodalität &amp;amp; Kontext:&lt;/strong&gt; Ein wesentlicher Vorteil gegenüber reinen Textmodellen oder klassischen OCR-Pipelines ist die Multimodalität von Gemini. Vereinfacht gesagt bedeutet das: Statt nur den rohen Text und dessen Koordinaten zu liefern (wie traditionelle OCR), kann Gemini Flash gleichzeitig den Text „lesen“ und das Seitenlayout „sehen“. Es „versteht“, wie Text in Spalten oder Tabellen angeordnet ist, erkennt Überschriften und kann Bilder oder Diagramme im Dokument interpretieren. Dadurch erfasst es den Kontext, den die reine Textreihenfolge oft nicht vermittelt, wesentlich besser. Dies ist gerade bei den komplexen und variantenreichen Layouts von Geschäftsberichten ein großer Vorteil. Gepaart mit dem langen Kontextfenster, das die Analyse umfangreicher Dokumentabschnitte am Stück erlaubt, ist dies ein entscheidender Fortschritt.&lt;/p&gt;

&lt;p&gt;Diese Kombination aus guter Qualität, hoher Geschwindigkeit, niedrigen Kosten und der Fähigkeit, Dokumente ganzheitlich zu verstehen, machte Gemini 2.0 Flash Lite zur guten Wahl für unseren produktiven Einsatz in Zusammenarbeit mit North Data.&lt;/p&gt;

&lt;h3 id=&quot;gemini-flash-in-aktion-der-workflow-mit-structured-outputs&quot;&gt;Gemini Flash in Aktion: Der Workflow mit Structured Outputs&lt;/h3&gt;

&lt;p&gt;Der Kern unseres Ansatzes kombiniert die Stärken von Gemini mit pragmatischen Lösungen, um auch mit den Eigenheiten sehr umfangreicher Dokumente umzugehen.&lt;/p&gt;

&lt;p&gt;Ein zentrales Problem stellen &lt;strong&gt;lange Geschäftsberichte&lt;/strong&gt; dar, die oft hunderte von Seiten umfassen. Das gesamte Dokument an Gemini zu übergeben, wäre zwar ideal für den Kontext, ist aber zu teuer für den Masseneinsatz. Um dieses Problem zu umgehen, haben wir einen mehrstufigen Ansatz entwickelt: Zuerst setzen wir nach wie vor auf bewährte &lt;strong&gt;OCR-Technologie&lt;/strong&gt;, um den reinen Text des gesamten Dokuments zu extrahieren. Dieser Rohtext dient uns dann als Basis für eine schnelle &lt;strong&gt;Voranalyse mittels Schlüsselwörtern&lt;/strong&gt;. Wir suchen nach Begriffen und Phrasen, die typischerweise auf relevante Abschnitte hindeuten, wie zum Beispiel „Consolidated Balance Sheet“, „Income Statement“ oder „Notes to the Financial Statements“.&lt;/p&gt;

&lt;p&gt;Basierend auf dieser Analyse wählen wir die &lt;strong&gt;bis zu 100 Seiten&lt;/strong&gt; aus, die am wahrscheinlichsten die gesuchten Finanzkennzahlen enthalten. &lt;em&gt;Nur dieser Auszug&lt;/em&gt; des Berichts wird dann als PDF-Kontext an Gemini Flash Lite übergeben. Dieser Kniff reduziert nicht nur die Verarbeitungskosten erheblich, sondern hilft auch, das Modell auf die wirklich wichtigen Teile des Dokuments zu konzentrieren und das „Rauschen“ irrelevanter Seiten zu minimieren.&lt;/p&gt;

&lt;p&gt;Nachdem wir die relevanten Seiten isoliert haben, beauftragen wir Gemini mit der gezielten Extraktion in ein vordefiniertes Format. Ein weiterer Baustein für präzise Ergebnisse ist hierbei die Nutzung von sogenannten &lt;strong&gt;Structured Outputs&lt;/strong&gt;. Gemini besitzt die Fähigkeit, nicht nur Text zu generieren, sondern direkt strukturierte JSON-Daten zu liefern, die einem vorgegebenen Schema folgen.&lt;/p&gt;

&lt;p&gt;Wir definieren dazu im Vorfeld ein klares Zielschema, das genau festlegt, welche Datenfelder wir erwarten und in welchem Format (wie etwa „Zahl“, „Text“, „Währungssymbol“). In Python nutzen wir dafür gerne Pydantic zur einfachen Definition und Validierung. Diese Struktur geben wir dem Modell explizit als Anweisung mit. Das ist nicht nur praktisch für die automatisierte Weiterverarbeitung, sondern verbessert auch nachweislich die Qualität: In unseren Tests führte allein dieser Schritt zu einer &lt;strong&gt;Verbesserung des Evaluations-Ergebnisses um rund 4 %&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hier ein vereinfachtes Python-Beispiel zur Illustration des Prinzips mit der &lt;code class=&quot;highlighter-rouge&quot;&gt;google-genai&lt;/code&gt; -Bibliothek und Structured Outputs:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;google&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;genai&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;google.genai&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;types&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pydantic&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BaseModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;genai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GEMINI_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# Define the desired output structure using Pydantic
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FinancialData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BaseModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;revenue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Total revenue reported for the fiscal year.&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;net_income&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Net income or profit after tax.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_assets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Total assets value.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fiscal_year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The ending year of the fiscal period.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;currency_symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Currency symbol used for major values (e.g., $, £, €).&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# Upload the relevant PDF pages (assuming 'selected_report_pages.pdf' was created by pre-filtering)
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;'selected_report_pages.pdf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
Please analyze the provided pages from the annual report PDF.
Extract the following financial figures for the main consolidated entity reported:
- Total Revenue
- Net Income (Profit after tax)
- Total Assets
- The Fiscal Year End
- The primary Currency Symbol used for the main financial figures (£, $, € etc.)

Return the data strictly adhering to the provided 'FinancialData' schema.
If a value cannot be found or determined confidently, leave the corresponding field null.
Pay close attention to units (e.g., thousands, millions).
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;generate_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gemini-2.0-flash-lite-001&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdf_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GenerateContentConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;response_mime_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;response_schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FinancialData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;extracted_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FinancialData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_validate_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extracted_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;f&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;An error occurred: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ein-blick-auf-die-zahlen-wie-gut-funktioniert-es-wirklich&quot;&gt;Ein Blick auf die Zahlen: Wie gut funktioniert es wirklich?&lt;/h3&gt;

&lt;p&gt;Um die tatsächliche Leistung unseres Ansatzes mit Gemini Flash objektiv zu bewerten, haben wir, wie erwähnt, einen Datensatz aus 100 manuell annotierten Geschäftsberichten erstellt. Dieser dient als Ground Truth, gegen den wir die Extraktionsergebnisse des Modells prüfen.&lt;/p&gt;

&lt;p&gt;Die Gesamtgenauigkeit über alle Kennzahlen und Berichte hinweg für unseren Ansatz lag bei &lt;strong&gt;83,5 %&lt;/strong&gt;. Dies waren die ersten Machbarkeitswerte für die Lösung, die wir bei North Data integriert haben. Das ist eine solide Basis und zeigt, dass der Ansatz grundsätzlich funktioniert. Interessanter wird es jedoch, wenn man sich die Genauigkeit für einzelne Kennzahlen ansieht:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Kennzahlen (Parameter)&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Genauigkeit&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Gesamt (Overall)&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;83.5%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;capital&lt;/td&gt;
      &lt;td&gt;96.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cash&lt;/td&gt;
      &lt;td&gt;95.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;employees&lt;/td&gt;
      &lt;td&gt;95.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;revenue&lt;/td&gt;
      &lt;td&gt;95.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;equity&lt;/td&gt;
      &lt;td&gt;98.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;currencySymbol&lt;/td&gt;
      &lt;td&gt;99.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;auditorName&lt;/td&gt;
      &lt;td&gt;89.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;materials&lt;/td&gt;
      &lt;td&gt;89.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;…&lt;/td&gt;
      &lt;td&gt;…&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;liabilities (creditors)&lt;/td&gt;
      &lt;td&gt;75.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;currentAssets&lt;/td&gt;
      &lt;td&gt;64.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;realEstate&lt;/td&gt;
      &lt;td&gt;60.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;receivables&lt;/td&gt;
      &lt;td&gt;52.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;tax&lt;/td&gt;
      &lt;td&gt;41.0%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;was-verrät-uns-diese-tabelle-und-wo-liegen-die-aktuellen-hürden&quot;&gt;Was verrät uns diese Tabelle und wo liegen die aktuellen Hürden?&lt;/h3&gt;

&lt;p&gt;Die Evaluationsergebnisse zeichnen ein klares Bild: Bei &lt;strong&gt;klar definierten Stammdaten oder Werten&lt;/strong&gt;, die in Geschäftsberichten oft prominent und relativ einheitlich ausgewiesen werden, erzielt das Modell sehr hohe Genauigkeitswerte. Dazu zählen beispielsweise &lt;code class=&quot;highlighter-rouge&quot;&gt;capital&lt;/code&gt; (Eigenkapital), &lt;code class=&quot;highlighter-rouge&quot;&gt;equity&lt;/code&gt; (Reinvermögen), die &lt;code class=&quot;highlighter-rouge&quot;&gt;employees&lt;/code&gt; (Anzahl der Mitarbeiter), &lt;code class=&quot;highlighter-rouge&quot;&gt;cash&lt;/code&gt; (Barmittel) oder das &lt;code class=&quot;highlighter-rouge&quot;&gt;currencySymbol&lt;/code&gt; (Währungssymbol). Erfreulicherweise sind &lt;strong&gt;Halluzinationen&lt;/strong&gt; – also das Erfinden von Zahlen, die im Dokument nicht existieren – in unseren Tests kein signifikantes Problem gewesen. Wenn Fehler auftraten, dann meist durch Fehlinterpretationen vorhandener Zahlen, nicht durch deren freie Erfindung.&lt;/p&gt;

&lt;p&gt;Schwieriger wird es für das Modell bei komplexeren Kennzahlen. Hier zeigen sich die Grenzen des aktuellen Ansatzes, insbesondere wenn es um &lt;strong&gt;semantische Unschärfe&lt;/strong&gt; und variierende Detailgrade geht. Viele Bilanzposten können in Berichten unterschiedlich definiert, benannt oder aufgeschlüsselt sein. Begriffe wie „Total Assets“ sind nicht immer absolut eindeutig – meint es die Bilanzsumme vor oder nach Abzug bestimmter Posten wie Goodwill, also den immateriellen Firmenwert?&lt;/p&gt;

&lt;p&gt;Die genaue Abgrenzung von &lt;code class=&quot;highlighter-rouge&quot;&gt;currentAssets&lt;/code&gt; (kurzfristige Vermögenswerte), &lt;code class=&quot;highlighter-rouge&quot;&gt;receivables&lt;/code&gt; (Forderungen) oder &lt;code class=&quot;highlighter-rouge&quot;&gt;liabilities&lt;/code&gt; (Verbindlichkeiten) variiert zwischen Unternehmen und Berichtsstandards. Hier stößt das Modell manchmal an seine Grenzen, die exakte, im jeweiligen Bericht gültige Definition allein aus dem unmittelbaren Kontext zu erschließen.&lt;/p&gt;

&lt;p&gt;Ebenso spielt die &lt;strong&gt;Abhängigkeit von Layouts&lt;/strong&gt; und der Platzierung von Informationen eine Rolle. Einige Werte, wie beispielsweise &lt;code class=&quot;highlighter-rouge&quot;&gt;realEstate&lt;/code&gt; (Immobilienvermögen), sind oft nicht prominent auf den Hauptseiten der Bilanz zu finden, sondern detailliert in den „Notes to the Financial Statements“ (Anhang) versteckt. Die Fähigkeit des Modells, solche Informationen über verschiedene Seiten und Layouts hinweg korrekt zuzuordnen, ist stark gefordert und führt zu niedrigeren Genauigkeitswerten.&lt;/p&gt;

&lt;p&gt;Schließlich erfordern manche Kennzahlen &lt;strong&gt;komplexere Interpretationen oder implizite Berechnungen&lt;/strong&gt;. Die Extraktion von Werten wie &lt;code class=&quot;highlighter-rouge&quot;&gt;tax&lt;/code&gt; (Steuern) ist hierfür ein gutes Beispiel. Oft spielen verschiedene Steuerarten (Ertragssteuern, Umsatzsteuern etc.) und latente Steuern eine Rolle, die über mehrere Abschnitte verteilt sein können. Die korrekte Zusammenführung und Interpretation dieser Informationen sind anspruchsvoll, was die aktuelle Genauigkeit von nur 41 % für diese Kennzahl erklärt.&lt;/p&gt;

&lt;p&gt;Diese quantitativen Ergebnisse bestätigen unsere qualitativen Beobachtungen: Das Modell ist hervorragend darin, klar benannte Informationen zu finden. Bei Mehrdeutigkeiten, stark variierenden oder komplexen Layouts und der Notwendigkeit, implizites Wissen oder Zusammenhänge über mehrere Textstellen hinweg zu verstehen, stößt es jedoch an Grenzen.&lt;/p&gt;

&lt;p&gt;Ein weiterer wichtiger Aspekt ist die &lt;strong&gt;variierende Genauigkeit zwischen verschiedenen Unternehmen&lt;/strong&gt;. Die Standardabweichung der Genauigkeit pro Unternehmen liegt bei etwa 9,2 %. Besonders auffällig ist, dass die Genauigkeit bei den sehr großen, oft hunderte Seiten umfassenden und individuell gestalteten Berichten von börsennotierten Unternehmen (PLCs) wie AstraZeneca (50 %), Barclays (65 %), HSBC (50 %), Shell (70 %) oder Unilever (55 %) teilweise deutlich abfällt.&lt;/p&gt;

&lt;p&gt;Tests mit unterschiedlich langen Ausschnitten aus den Berichten zeigten, dass die Länge des zu bewältigenden Kontextes für Gemini keine größere Schwierigkeit darstellt. Wir gehen daher davon aus, dass vor allem die Einzigartigkeit der Berichtsstrukturen dieser Konzerne für das Modell herausfordernd sind. Während Gemini Flash Lite gut mit Layouts zurechtkommt, die oft von kleineren Unternehmen mit Standardsoftware erstellt werden, sind diese komplexen Fälle eine größere Hürde. Eine Erklärung könnte sein, dass es die vom Standard abweichenden Berichte seltener in Geminis Trainingsdaten geschafft haben.&lt;/p&gt;

&lt;p&gt;Ein weiteres wiederkehrendes Problem ist die korrekte Erfassung von &lt;strong&gt;Einheiten und Skalierungen&lt;/strong&gt;. Das Übersehen oder die Fehlinterpretation von Angaben wie „in Tausend £“ oder „Millions USD“ führt zu extrahierten Werten, die um Faktoren von 1.000 oder 1.000.000 falsch sind. Hier sind robuste nachgelagerte Validierungsregeln und gezieltes Prompting notwendig, um das Modell für diese Details zu sensibilisieren.&lt;/p&gt;

&lt;p&gt;Auch die Darstellung &lt;strong&gt;negativer Zahlen&lt;/strong&gt;, die in Geschäftsberichten oft durch Klammern erfolgt (z.B. „(1.234)“ statt „-1.234“), erfordert einen expliziten Hinweis im Prompt, damit das Modell diese Konvention korrekt interpretiert und die Zahlen mit dem richtigen Vorzeichen extrahiert. Wie bereits gesagt stellen Halluzinationen (im Gegensatz zu älteren Modellen) hier keine großen Probleme dar, bloß die Interpretation der Zahlen gelingt nicht immer.&lt;/p&gt;

&lt;p&gt;Zu guter Letzt stehen wir auch vor dem klassischen Trade-off zwischen Kosten und Leistung bei besonders komplexen Fällen. Anspruchsvollere Reasoning-Ansätze wie Chain-of-Thought (CoT), bei denen das Modell seine „Gedankenschritte“ explizit macht, oder der Einsatz noch größerer und leistungsfähigerer Modelle (z.B. Gemini 2.5 Pro) könnten bei den genannten Problemen, insbesondere bei den komplexen Berichten, Abhilfe schaffen.&lt;/p&gt;

&lt;p&gt;Diese sind jedoch aktuell oft noch deutlich teurer. So ist beispielsweise &lt;strong&gt;Gemini 2.5 Pro derzeit 16- bis 32-mal so teuer wie das von uns genutzte Gemini 2.0 Flash Lite&lt;/strong&gt;. Auch das sehr gängige GPT-4.1, welches in ChatGPT zum Einsatz kommt, kostet mit 2 $ pro 1 Million Input Tokens ca. 27-mal so viel wie Gemini 2.0 Flash Lite. Die Verarbeitung eines durchschnittlichen Berichts aus unserem Testdatensatz mit 30 Seiten kostet mit unserer Lösung daher nur ca. 0,0007 $!&lt;/p&gt;

&lt;h3 id=&quot;fazit-gemini-flash-als-leistungsstarke-ergänzung-im-werkzeugkasten&quot;&gt;Fazit: Gemini Flash als leistungsstarke Ergänzung im Werkzeugkasten&lt;/h3&gt;

&lt;p&gt;Gemini Flash hat sich für uns als nützlicher Baustein erwiesen, um die Extraktion strukturierter Daten aus Geschäftsberichten auf ein neues Level zu heben und in den produktiven Einsatz bei North Data zu bringen. Es ersetzt nicht zwangsläufig die gesamte klassische Pipeline (wie unsere OCR-Vorfilterung zeigt), aber es bietet eine enorm leistungsfähige, integrierte Alternative für den Kernprozess der intelligenten Datenextraktion und -strukturierung.&lt;/p&gt;

&lt;p&gt;Die Fähigkeit, Layouts zu verstehen, über einen größeren Kontext zu arbeiten und direkt strukturierte Outputs zu liefern, reduziert die Komplexität und den Wartungsaufwand im Vergleich zu traditionellen, mehrstufigen Ansätzen erheblich. Die Herausforderungen bleiben, aber der Fortschritt ist deutlich und eröffnet neue Möglichkeiten für die automatisierte Finanzdatenanalyse.&lt;/p&gt;

&lt;p&gt;Wir sind gespannt, wie sich diese Technologie weiterentwickelt und welche neuen Lösungsansätze sich ergeben. Habt ihr ähnliche Erfahrungen gemacht oder andere Strategien entwickelt? Teilt eure Gedanken mit uns!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dieser Blogpost wurde mit Unterstützung von Gemini-2.5-Pro geschrieben.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://getomni.ai/ocr-benchmark&quot; target=&quot;_blank&quot;&gt;OmniAI OCR Benchmark&lt;/a&gt;, abgerufen am 17.06.25 &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://blog.cronn.de/de/ai/largelanguagemodels/2023/07/26/analyse-von-geschaeftsberichten-mit-chatgpt-1.html&quot; target=&quot;_blank&quot;&gt;cronn Blog: Analyse von Geschäftsberichten mit ChatGPT – Teil 1&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-0-flash-lite?hl=de&quot; target=&quot;_blank&quot;&gt;Dokumentation Google Gemini 2.0 Flash-Lite&lt;/a&gt;, abgerufen am 17.06.25 &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://web.archive.org/web/20230614104237/https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;Web Archive: OpenAI-Preise vom 14. Juni 2023&lt;/a&gt;, abgerufen am 17.06.25 &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://ai.google.dev/gemini-api/docs/pricing?hl=de&quot; target=&quot;_blank&quot;&gt;Preise für die Gemini Developer API&lt;/a&gt;, abgerufen am 17.06.25 &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>leonardThiele</name></author><summary type="html">Wir zeigen einen KI-Use-Case im Einsatz: Extraktion von Kennzahlen aus Geschäftsberichten mittels LLM.</summary></entry><entry xml:lang="en"><title type="html">Code Generation using Java Annotation Processing</title><link href="https://blog.cronn.de/en/java/codegeneration/2025/05/21/annotation-processing-code-generation.html" rel="alternate" type="text/html" title="Code Generation using Java Annotation Processing" /><published>2025-05-21T00:00:00-05:00</published><updated>2025-05-21T00:00:00-05:00</updated><id>https://blog.cronn.de/en/java/codegeneration/2025/05/21/annotation-processing-code-generation</id><content type="html" xml:base="https://blog.cronn.de/en/java/codegeneration/2025/05/21/annotation-processing-code-generation.html">&lt;h3 id=&quot;introduction-to-code-generation&quot;&gt;Introduction to code generation&lt;/h3&gt;

&lt;p&gt;Developers often find themselves confronted with writing the same type of simple code over and over again. Over time, some options were designed to reduce the time needed for writing trivial code. IDEs can automatically generate getters and setters or even apply custom templates that can be used for code generation. Elaborate tools like the OpenAPI Generator &lt;sup id=&quot;fnref:openapi&quot;&gt;&lt;a href=&quot;#fn:openapi&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; are able to create the groundwork for client and server code in REST-based communication by using the interface specification as input, and even more recently elaborate AIs have been launched with this purpose in mind. In general, there are two different types of generating code: one time generation, like the getter and setter creation from IDE, and continuous generation, like the OpenAPI generator. In the latter, a change of interface specification directly results in changes in the generated code, and thus specification and code remain in sync.&lt;/p&gt;

&lt;p&gt;Java annotation processing, which was introduced in Java 1.6, is another example of continuous generation. The main idea is that a code generator operates on specific parts of the code which is marked by annotations. These annotations are then processed in the generator, where new code is generated based on the annotated code and the annotations themselves. One of the most prominent frameworks that incorporates annotation processing is Project Lombok &lt;sup id=&quot;fnref:lombok&quot;&gt;&lt;a href=&quot;#fn:lombok&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; which, among other features, has the option of generating getters and setters via annotation processing. The advantage of annotation processing is that the new methods are only created in the generated code and are not present in the actual versioned code, which in turn is more precise and contains less trivial boilerplate code. Furthermore, the generated code does not become obsolete and thus requires no maintenance.&lt;/p&gt;

&lt;h3 id=&quot;using-an-existing-annotation-processor&quot;&gt;Using an existing annotation processor&lt;/h3&gt;

&lt;p&gt;An annotation processor is in most cases already present if one is using third party libraries. The process of using it as code generator is easily described through the following example: suppose you want to map an object of type &lt;code class=&quot;highlighter-rouge&quot;&gt;Company&lt;/code&gt; to its DTO &lt;code class=&quot;highlighter-rouge&quot;&gt;CompanyDto&lt;/code&gt;. MapStruct &lt;sup id=&quot;fnref:mapstruct&quot;&gt;&lt;a href=&quot;#fn:mapstruct&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; enables simple mapping of different types through generated classes which are described by annotations on an interface used as base.&lt;/p&gt;

&lt;p&gt;Let us look at a Definition of a MapStruct mapper for a Company object to &lt;code class=&quot;highlighter-rouge&quot;&gt;CompanyDto&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: CompanyMapper.java&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Mapper&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompanyMapper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nc&quot;&gt;CompanyMapper&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;INSTANCE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mappers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CompanyMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@Mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;companyName&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;nd&quot;&gt;@Mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;companyAge&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;nc&quot;&gt;CompanyDto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The actual usage of the mapper from above looks like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: CompanyMapperTest.java&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mapCompanyToDto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cronn GmbH&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;CompanyDto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompanyMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;INSTANCE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCompanyName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cronn GmbH&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCompanyAge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In order to use an annotation processor (in this case MapStruct) it is necessary to inform the build tool that such a processor is present and should be used. Gradle, for example, employs the keyword “annotationProcessor” for this, as is shown below.&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: build.gradle&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;annotationProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;org.mapstruct:mapstruct-processor:${mapstructVersion}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using the above definition MapStruct then creates an implementation for the interface using the information given through the annotations. The output for this is shown below.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Generated File: CompanyMapperImpl.java&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompanyMapperImpl&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompanyMapper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompanyDto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyAge&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;companyName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;companyAge&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;CompanyDto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyDto&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompanyDto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyAge&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyDto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Through annotation processing, an interesting aspect of the Java compilation step becomes visible. Normally, the compilation to bytecode starts with the parsing step, continues with an analyzing step and ends with the bytecode generation (note that this is an oversimplification for the needs of this article). Annotation processing is directly incorporated into this process. After the parsing step, all relevant annotations are processed by processors and if new code has been generate the parsing step is restarted. By repeating these steps in multiple rounds it is possible to generate code in one annotation processor which itself contains annotations which may trigger further processors in following rounds. This is nicely illustrated in the OpenJDK article on Compilation Overview &lt;sup id=&quot;fnref:compilation&quot;&gt;&lt;a href=&quot;#fn:compilation&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;figure&gt;
&lt;img data-src=&quot;/img/posts/annotation-processing-code-generation-grafik.avif&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Diagram of the JavaCompiler flow.&quot; /&gt;
&lt;figcaption class=&quot;long-fig-caption&quot;&gt; The compilation process contains a repetition in case annotation processors generate new source material. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;custom-code-generator-for-annotation-processing&quot;&gt;Custom code generator for annotation processing&lt;/h3&gt;

&lt;p&gt;The usage of existing annotation processors from third party libraries is already a big improvement for typical situations. However, the more interesting application is the development and use of custom generators. For this purpose, Java offers the &lt;code class=&quot;highlighter-rouge&quot;&gt;javax.annotation.processing.Processor&lt;/code&gt; interface &lt;sup id=&quot;fnref:processor&quot;&gt;&lt;a href=&quot;#fn:processor&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, which is already implemented in the abstract class &lt;code class=&quot;highlighter-rouge&quot;&gt;javax.annotation.processing.AbstractProcessor&lt;/code&gt;. When creating a custom annotation processor either this interface has to be implemented, or the abstract class has to be extended in order to inform the compilation unit to use it. Through this, the custom processor inherits, among others, the methods &lt;code class=&quot;highlighter-rouge&quot;&gt;getSupportedAnnotationTypes&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One of the first steps in creating a custom annotation processor is to tell the compilation unit, which annotations are handled by this processor. When inheriting from &lt;code class=&quot;highlighter-rouge&quot;&gt;AbstractProcessor&lt;/code&gt;, instead of implementing &lt;code class=&quot;highlighter-rouge&quot;&gt;getSupportedAnnotationTypes&lt;/code&gt; with a custom implementation, the supported annotations can be configured with the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@SupportedAnnotationTypes&lt;/code&gt;, which is used on the processor itself. Here, it is possible to use existing annotations as well as custom annotations specifically created for use with this processor. It is even possible to use wildcards for this.&lt;/p&gt;

&lt;p&gt;The following example shows a custom annotation and how this is used in a custom annotation processor.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: Builder.java&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SOURCE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The custom annotation processor using the custom annotation from above looks like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: BuilderAnnotationProcessor.java&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@SupportedAnnotationTypes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.example.Builder&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BuilderAnnotationProcessor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractProcessor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;?&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TypeElement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoundEnvironment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roundEnv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method is the one which is called by the compilation unit and where the actual generation happens. A set of all configured annotations and a &lt;code class=&quot;highlighter-rouge&quot;&gt;RoundEnvironment&lt;/code&gt; for the current processing round are given as parameters. In order to get all elements that are currently annotated with the configured annotations, the round environment can be used by calling its method &lt;code class=&quot;highlighter-rouge&quot;&gt;getElementsAnnotatedWith(…)&lt;/code&gt;. Depending on the target, on which the annotation is specified, the returned elements may be of different element types like classes, fields or methods (e.g. in the upper example for &lt;code class=&quot;highlighter-rouge&quot;&gt;@org.example.Builder&lt;/code&gt; the target &lt;code class=&quot;highlighter-rouge&quot;&gt;ElementType.TYPE&lt;/code&gt; was used, which specifies classes, interfaces, enums and records).&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: BuilderAnnotationProcessor.java&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;?&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TypeElement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;annotations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoundEnvironment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roundEnv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classElement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roundEnv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getElementsAnnotatedWith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;className&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classElement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSimpleName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the actual Java file creation, a &lt;code class=&quot;highlighter-rouge&quot;&gt;Filer&lt;/code&gt; &lt;sup id=&quot;fnref:filer&quot;&gt;&lt;a href=&quot;#fn:filer&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; instance can be used, which already has information about the build location for newly created files. Additionally, to the &lt;code class=&quot;highlighter-rouge&quot;&gt;RoundEnvironment&lt;/code&gt; mentioned above, when inheriting from &lt;code class=&quot;highlighter-rouge&quot;&gt;AbstractProcessor&lt;/code&gt;, a &lt;code class=&quot;highlighter-rouge&quot;&gt;ProcessingEnvironment&lt;/code&gt; also exists, which can be accessed from child classes and be used in order to get such a &lt;code class=&quot;highlighter-rouge&quot;&gt;Filer&lt;/code&gt; instance for creating new source files.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// File: BuilderAnnotationProcessor.java&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;JavaFileObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sourceFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processingEnv&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getFiler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createSourceFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getSourceFileName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Writer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BufferedWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sourceFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;openWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;generateSourceCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(...));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// handle exception&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The final code that is generated by the processor, is just text that is written with the file writer of the filer instance. Therefore, it can be created in different well-known ways, e.g. by using string concatenation, &lt;code class=&quot;highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt;s or multi-line strings with formatters. However, if its content gets too complex, dedicated frameworks like &lt;sup id=&quot;fnref:javapoet&quot;&gt;&lt;a href=&quot;#fn:javapoet&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; or more elaborate techniques like StringTemplates &lt;sup id=&quot;fnref:stringtemplate&quot;&gt;&lt;a href=&quot;#fn:stringtemplate&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; are advised. The custom code generator in our example code &lt;sup id=&quot;fnref:examplecode&quot;&gt;&lt;a href=&quot;#fn:examplecode&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; builds up on StringTemplates and shows some of the capabilities there. As mentioned previously, the generation process repeats itself in the case of newly created files also containing annotations for which annotation processors exist.&lt;/p&gt;

&lt;p&gt;As described above, it is important to note that the complete annotation processing happens during the compilation step. If debugging is desired it is therefore necessary to add debug information to this, for example in the case of Gradle, by adding the &lt;code class=&quot;highlighter-rouge&quot;&gt;-Dorg.gradle.debug=true&lt;/code&gt; flag to the current Gradle task. Through this, it is possible to use typical debugging tools, which make the development of such a code generator as simple as regular code.&lt;/p&gt;

&lt;p&gt;In order to use the custom annotation processor during compilation of the target code, the compiler has to be informed about the existence of a processor to be used. There are different ways to achieve this, ranging from specific javac options like &lt;code class=&quot;highlighter-rouge&quot;&gt;javac -processor …&lt;/code&gt;, to maven plugins. It is also possible to register it in the meta information of the build jar file in a file typically named &lt;code class=&quot;highlighter-rouge&quot;&gt;META-INF/services/javax.annotation.processing.Processor&lt;/code&gt;, where each annotation processor is listed line by line. This is also the solution used in our example code. To make this process even easier, Google AutoService library &lt;sup id=&quot;fnref:googleauto&quot;&gt;&lt;a href=&quot;#fn:googleauto&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; automatically creates such a file (interestingly enough, by using annotations and generating the file through annotation processing).&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// File: META-INF/services/javax.annotation.processing.Processor
org.example.BuilderAnnotationProcessor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Many possibilities exist for automatic generation of simple code in Java. This article presented Annotation Processing, which is easy to use and deeply integrated in the Java environment. Potential applications range from builder classes, object mappers between different domain models, (fluent) setters and getters, automatic generation of constructors and boilerplate methods such as &lt;code class=&quot;highlighter-rouge&quot;&gt;toString()&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;hashCode()&lt;/code&gt;. All of which can be used by adding a single annotation to the target code. Our example code &lt;sup id=&quot;fnref:examplecode:1&quot;&gt;&lt;a href=&quot;#fn:examplecode&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; demonstrates the usage of existing third party libraries as well as the creation of custom annotations and generators. Due to the mentioned versatility and ease-of-use annotation processing is a powerful tool in the Java ecosystem.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:openapi&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://openapi-generator.tech/&quot; target=&quot;_blank&quot;&gt;OpenAPI Generator&lt;/a&gt; &lt;a href=&quot;#fnref:openapi&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:lombok&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://projectlombok.org/&quot; target=&quot;_blank&quot;&gt;Project Lombok&lt;/a&gt; Note: unlike typical annotation processors, in order to fulfill all its goals, Project Lombok directly manipulates the &lt;code class=&quot;highlighter-rouge&quot;&gt;.class&lt;/code&gt; files instead of creating new &lt;code class=&quot;highlighter-rouge&quot;&gt;.java&lt;/code&gt; files first &lt;a href=&quot;#fnref:lombok&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:mapstruct&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://mapstruct.org/&quot; target=&quot;_blank&quot;&gt;MapStruct&lt;/a&gt; &lt;a href=&quot;#fnref:mapstruct&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:compilation&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://openjdk.org/groups/compiler/doc/compilation-overview/index.html&quot; target=&quot;_blank&quot;&gt;OpenJDK compilation overview&lt;/a&gt; &lt;a href=&quot;#fnref:compilation&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:processor&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.compiler/javax/annotation/processing/Processor.html&quot; target=&quot;_blank&quot;&gt;Java Processor interface documentation&lt;/a&gt; &lt;a href=&quot;#fnref:processor&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:filer&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.compiler/javax/annotation/processing/Filer.html&quot; target=&quot;_blank&quot;&gt;Java Filer documentation&lt;/a&gt; &lt;a href=&quot;#fnref:filer&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:javapoet&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/palantir/javapoet&quot; target=&quot;_blank&quot;&gt;JavaPoet&lt;/a&gt; &lt;a href=&quot;#fnref:javapoet&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:stringtemplate&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.stringtemplate.org/&quot; target=&quot;_blank&quot;&gt;StringTemplate&lt;/a&gt; &lt;a href=&quot;#fnref:stringtemplate&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:examplecode&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/cronn/annotation-processing-blog-post-example&quot; target=&quot;_blank&quot;&gt;Example code&lt;/a&gt; &lt;a href=&quot;#fnref:examplecode&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:examplecode:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:googleauto&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/google/auto/tree/main/service&quot; target=&quot;_blank&quot;&gt;Google AutoService&lt;/a&gt; &lt;a href=&quot;#fnref:googleauto&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>kevinKeul</name></author><summary type="html">Writing trivial code can be cumbersome and can reduce code clarity. Here we show how Java's Annotation Processing can help.</summary></entry><entry xml:lang="en"><title type="html">Handling of the ‘this-escape’ warning in JDK 21</title><link href="https://blog.cronn.de/en/java/2024/08/13/this-escape.html" rel="alternate" type="text/html" title="Handling of the 'this-escape' warning in JDK 21" /><published>2024-08-13T00:00:00-05:00</published><updated>2024-08-13T00:00:00-05:00</updated><id>https://blog.cronn.de/en/java/2024/08/13/this-escape</id><content type="html" xml:base="https://blog.cronn.de/en/java/2024/08/13/this-escape.html">&lt;p&gt;JDK version 21 introduced a new rule to the Java linter. According to this rule it is not permitted to call an overridable method within the constructor of a class &lt;sup id=&quot;fnref:jdk-bug&quot;&gt;&lt;a href=&quot;#fn:jdk-bug&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. If this rule is disregarded and the Java code compiled using the &lt;span style=&quot;white-space: pre;&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;-Xlint:all&lt;/code&gt;&lt;/span&gt; or &lt;span style=&quot;white-space: pre;&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;-Xlint:this-escape&lt;/code&gt;&lt;/span&gt; flag, this leads to the following &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;warning: [this-escape] possible `this` escape before subclass is fully initialized
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;You can jump to the three approaches here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;#Using the keywords `final`, `private` or `static`&quot;&gt;Using the keywords &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;#Usage of the annotation `@PostConstruct`&quot;&gt;Usage of the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;#Revise the class design&quot;&gt;Revise the class design&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;
&lt;p&gt;The addition of the new rule to the Java linter in JDK 21 is a good improvement as it helps prevent code smell. It has long been recommended to avoid calling overridable methods from the constructor &lt;sup id=&quot;fnref:effective-java&quot;&gt;&lt;a href=&quot;#fn:effective-java&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:java-doc&quot;&gt;&lt;a href=&quot;#fn:java-doc&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. However, as an analysis &lt;sup id=&quot;fnref:analysis&quot;&gt;&lt;a href=&quot;#fn:analysis&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; of some well-known open source projects shows, there are still places in the code where the recommendation is forgotten or ignored. Even in our own projects the upgraded Java linter was also able to find a few places that did not follow the recommendation.&lt;/p&gt;

&lt;p&gt;In this article, we will briefly look at why no overridable methods should be called in the constructor. The following three sections show approaches  that were used to resolve the warning in our projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the following, it is assumed that the code shown is always compiled with the flag &lt;span style=&quot;white-space: pre;&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;-Xlint:all&lt;/code&gt;&lt;/span&gt;, even if this was not explicitly specified. The complete code is available in this &lt;a href=&quot;https://github.com/cronn/this-escape-blog-post-example/&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;origin-story&quot;&gt;Origin Story&lt;/h3&gt;
&lt;p&gt;The rationale for the &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning is explained below. Using an example, let’s take a look at the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt;. The class has an instance variable &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; and a public non-final method &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt;. The &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; method is called in the constructor of the class. The code compiles fine with JDK 17, but when compiling with JDK 21, the Java linter issues a &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;stranger&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Calls overrideable method, causes this-escape warning&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; class itself is unproblematic, but as soon as the class is extended, it can lead to errors that are difficult to find. The Java linter warns of this with the &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning. To be able to provoke an error, we also create the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; as an extension of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt;. The class &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; adds another instance variable, &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt;, and overrides the method &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Musician&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;triangle&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I heard you play &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;. Awesome!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What is now being output when a new &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; object is created with the &lt;code class=&quot;highlighter-rouge&quot;&gt;new Musician(&quot;Jimi&quot;, &quot;guitar&quot;)&lt;/code&gt; statement? When an instance of &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; is created, the constructor of &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; is called in the constructor of &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt;. In the constructor of &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt;, the instance variable &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; is initialized and then the method &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; is called. The variable &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt; is then initialized within the constructor of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt;. The statement results in the following output:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hello Jimi!
I heard you play null. Awesome!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The overridden method &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; is called from &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; even before &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; has been fully instantiated. This results in the value &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt; being output for &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt;, although &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt; can never have the value &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt; after instantiation of the object &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt;. The reason for the incorrect output is quickly apparent in the example. Nevertheless, it shows that a class should not call any overridable methods of its own class in the constructor, as the class cannot ensure that it is in a consistent state when the method is called. It follows that the &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; method should not be both overridable and called by the constructor at the same time.&lt;/p&gt;

&lt;p&gt;It should be noted that the error in this example seems obvious as we have looked at a simple example to explain the situation. In practice, the error in an extensive class within a complex class hierarchy with further inheritance and nesting in connection with concurrency can be considerably more difficult to locate.&lt;/p&gt;

&lt;h3 id=&quot;three-approaches&quot;&gt;Three approaches&lt;/h3&gt;
&lt;p&gt;The following three sections present ways of preventing or circumventing the calling of an overridable method from the constructor.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;Using the keywords `final`, `private` or `static`&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;using-the-keywords-final-private-or-static&quot;&gt;Using the keywords &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The most direct way to prevent the &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning is to prohibit the overwriting of all methods called by the constructor. This can be achieved in Java with the keywords &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;. If a class is declared as &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, it is no longer possible to extend it. Accordingly, none of its methods can be overwritten. The declaration of a method as &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt; ensures that it is the method alone which cannot be overwritten.&lt;/p&gt;

&lt;p&gt;We can use these keywords  to fix the incorrect output of the &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; classes from the last section in various ways. In the following, we first declare the &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; method of &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; as &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; to satisfy the Java linter.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;stranger&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Method is now final&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This makes it so that the &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; class can no longer overwrite the &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; method. Instead, a separate method &lt;code class=&quot;highlighter-rouge&quot;&gt;printInstrument()&lt;/code&gt; is defined in the &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; class, which is now responsible for the output of the instrument. For this approach to work, we must define that the class &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; should not be extended by any other class, so we add the keyword &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; to the declaration of the class – otherwise, the Java linter would give us a &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning here too.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Class is now final&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Musician&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;triangle&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printInstrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;printInstrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I heard you play &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;. Awesome!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the changes, the statement &lt;code class=&quot;highlighter-rouge&quot;&gt;new Musician(&quot;Jimi&quot;, &quot;guitar&quot;)&lt;/code&gt; leads to the following output:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hello Jimi!
I heard you play guitar! Awesome!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, it is not always possible to declare a class as &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; or method as &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;. If the class is managed by a dependency injection framework, such as Spring or Quarkus, the call of overridable methods from the constructor can usually be bypassed in another way. We will look at these in the next section.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;Usage of the annotation `@PostConstruct`&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;usage-of-the-annotation-postconstruct&quot;&gt;Usage of the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Although we will be using Spring in the following examples, the approach can also be used for other dependency injection frameworks that implement the &lt;em&gt;Jakarta Contexts and Dependency Injection&lt;/em&gt; specification or the &lt;em&gt;Jakarta Annotations&lt;/em&gt; specification. Part of the &lt;em&gt;Jakarta Annotations&lt;/em&gt; specification is the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostContruct&lt;/code&gt;, which is essential for the approach presented here. Using the annotation, we can link into the life cycle of a bean managed by Spring. In the case of &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; this happens, as the name suggests, after the constructor has been executed and the bean has been fully initialized. This makes it possible to move the call of an overridable method from the constructor to a safe place. Spring offers other ways to insert custom code into the lifecycle of a bean, but the use of &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; is the recommended &lt;sup id=&quot;fnref:spring-lifecycle&quot;&gt;&lt;a href=&quot;#fn:spring-lifecycle&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, so only this will be discussed here.&lt;/p&gt;

&lt;p&gt;In order to illustrate the use of &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;, let us extend our earlier example. In the previous example, the instrument &lt;em&gt;triangle&lt;/em&gt; was assigned to each musician if no instrument was specified. We want to optimize this a little by making it possible to connect an external resource. This should provide a mapping between known musicians, represented by their name, and their instrument. The mapping should be saved in a cache for faster access. The use of the cache is shown schematically in the following listing:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Get name of a musician from somewhere&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getInstrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Get instrument from somewhere&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/*
         musicianInstrumentCache contains a mapping of the form:
         Jimi -&amp;gt; guitar
         Miles -&amp;gt; trumpet
         Ludwig -&amp;gt; piano
         ...
    */&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;musicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;musician&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We create two classes for the implementation. The abstract class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; contains a simple cache, which was realized as a &lt;code class=&quot;highlighter-rouge&quot;&gt;Map&lt;/code&gt; with the mapping musician name ⟼ instrument, and calls the method &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; in the constructor. The &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; method is to be used by the specializations of &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; (see below) to read in an external resource and update the cache. The following applies to the &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; method:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Calling the method should enable other classes to update the cache at runtime . The method should therefore be public.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For different types of resources, such as external files, databases, etc., it should be possible to create different specializations of &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;, which override the &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; method in line with the resource used. Therefore, the method should be abstract and cannot be declared as &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation of the abstract class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; is given below. The linter issues a &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning when the class is compiled, as the abstract method &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; is called in the constructor.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MusicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MusicianInstrumentCache.init()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Calls overrideable method, causes this-escape warning&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Should be public and abstract&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before we address the problem, let’s look at a specialization of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. The class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; shows a possible specialization of &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. The class should read the mapping from a file via the Spring-injected &lt;code class=&quot;highlighter-rouge&quot;&gt;ResourceLoader&lt;/code&gt;, then proceed to write it to the cache. To keep the example short, the reading of the file and the writing to the cache is only implied in the code.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileBasedMusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;classpath:mapping.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FileBasedMusicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FileBasedMusicianInstrumentCache.init()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resourceLoader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FileBasedMusicianInstrumentCache.updateCache()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Logic for importing mapping and adding it to the cache. Briefly,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// represented by the following lines without exception handling:&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Resource&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContentAsString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StandardCharsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UTF_8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\n&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// getter and setter&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It should be noted that if the &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; method of the &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; class is called in the constructor of the &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; class, then the &lt;code class=&quot;highlighter-rouge&quot;&gt;resourceLoader&lt;/code&gt; has not yet been set. This is because, as described in the section &lt;em&gt;Origin Story&lt;/em&gt;, the constructor of the extending class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; calls the constructor of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; as the first statement, even if the call via &lt;code class=&quot;highlighter-rouge&quot;&gt;super()&lt;/code&gt; was not explicitly specified in the Java code. This can also be seen in the output, where &lt;em&gt;FileBasedMusicianInstrumentCache.updateCache()&lt;/em&gt; is written to the console before &lt;em&gt;FileBasedMusicianInstrumentCache.init()&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MusicianInstrumentCache.init()
FileBasedMusicianInstrumentCache.updateCache()
FileBasedMusicianInstrumentCache.init()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Fortunately, the error and the &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning can be fixed with the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; without major adjustments, so that the overridable method &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; is no longer called before the object has been completely initialized. It is sufficient to annotate the &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; method in the &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; class with &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;. The call of the method &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; can be removed from the constructor, as Spring is now responsible for the call. The class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; can remain unchanged, as Spring checks whether a method with &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; is annotated in a superclass and adopts the behaviour for the subclasses.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MusicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MusicianInstrumentCache.init()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Remove importMapping() method call here&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@PostConstruct&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Add annotation&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the application is started, the constructor &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; is still called when the class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; is initialized, but the method &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; is no longer called in the constructor; instead, the constructor of &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; is completed first. Only after &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; has been completely constructed does Spring call the &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; method annotated with &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;. This results in the following output:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MusicianInstrumentCache.init()
FileBasedMusicianInstrumentCache.init()
FileBasedMusicianInstrumentCache.updateCache()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The procedure with &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; makes it possible to link the use of overridable methods to the creation of the object without the problems that may result when calling from the constructor. However, this requires the use of a dependency injection framework that supports the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The previous two sections described two small tweaks to  satisfy the linter. In the next section we will look at another way of dealing with the warning.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;Revise the class design&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;revise-the-class-design&quot;&gt;Revise the class design&lt;/h4&gt;
&lt;p&gt;Sometimes the &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning can also serve as a suggestion to re-evaluate the class design. Depending on the result of the evaluation, the necessary changes may have a greater impact on the structure of the code than was the case with the other two methods. We once again take up the example from the previous section to show what an adaptation of the class design could look like.&lt;/p&gt;

&lt;p&gt;In the last section, the two classes &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; were created, with the latter extending the former. Due to inheritance, the method &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; had to be public and overridable, which ultimately led to the &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning. In the following, the class design should use composition instead of inheritance.&lt;/p&gt;

&lt;p&gt;The functionality of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; is split for this purpose. The management of the cache will remain the task of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. The import of an external resource is outsourced to the class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt;. The class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt; also receives a reference to an instance of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. Below, the old, inheritance-based class design is compared to the new class design in a UML class diagram.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Inheritance                           Composition
=========                             ===========
┌────────────────────────────────┐   ┌────────────────────────────────────────┐
│           &amp;lt;abstract&amp;gt;           │   │                                        │
│    MusicianInstrumentCache     │   │        MusicianInstrumentCache         │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│#cache:Map&amp;lt;String,String&amp;gt;       │   │-cache:Map&amp;lt;String,String&amp;gt;               │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│+importMapping():void &amp;lt;abstract&amp;gt;│   │~put(name:String,instrument:String):void│
│+getInstrumentFor(String):String│   │+getInstrumentFor(String):String        │
└────────────────────────────────┘   └────────────────────────────────────────┘
                 ▲                                       ^
                 │                                       │
                 │                                       │ -cache
                 │                                       │
┌────────────────┴───────────────┐   ┌───────────────────┴────────────────────┐
│FileBasedMusicianInstrumentCache│   │  FileBasedMusicianInstrumentImporter   │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│-resourceLoader:ResourceLoader  │   │-resourceLoader:ResourceLoader          │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│+importMapping():void           │   │+importMapping():void                   │
└────────────────────────────────┘   └────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The following listing shows the code of the new class &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. The class has two methods, one for reading and one for writing the cache. &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; was annotated with &lt;code class=&quot;highlighter-rouge&quot;&gt;@Component&lt;/code&gt;, as it is managed by the Dependency Injection Framework, and is to be injected into &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The code of the class &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt; is shown in the following listing. The annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; is no longer required as it is now sufficient to declare the class  as &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;classpath:mapping.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                                               &lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resourceLoader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;importMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;importMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Logic for importing mapping and adding it to the cache. Briefly,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// represented by the following lines without exception handling:&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Resource&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContentAsString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StandardCharsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UTF_8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\n&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// getter and setter&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The example is intended to provide an impression of what a revision of the class design could look like. However, this does not always have to involve a switch from inheritance to composition. It could also involve extracting/moving methods, or using a creational pattern  to resolve the call of an overridable method from the constructor.&lt;/p&gt;

&lt;p&gt;At this point, we have looked at all the approaches  that were used to upgrade our project. The next section summarizes the main points of this article.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;In this post, we described the motivation behind the Java linter’s &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; warning and showed three ways to prevent said warnings. The possibilities are listed below:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use of the keywords &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Use of the annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Revision of the class design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is not always the case that all three approaches are applicable. Sometimes a combination of multiple approaches is necessary to resolve the warning. The best way to deal with the warning must be decided on a case-by-case basis. In most cases the first or second approach should be sufficient; however, the use of the second approach requires that the affected class is managed by a dependency injection framework such as Spring or Quarkus. Reworking the class design should always lead to success, but is also the most time-consuming.&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:jdk-bug&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://bugs.openjdk.org/browse/JDK-8015831&quot;&gt;Add lint check for calling overridable methods from a constructor&lt;/a&gt; &lt;a href=&quot;#fnref:jdk-bug&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:effective-java&quot;&gt;
      &lt;p&gt;Joshua Bloch. 2001. Effective Java programming language guide. Sun Microsystems, Inc., USA. &lt;a href=&quot;#fnref:effective-java&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:java-doc&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/final.html&quot;&gt;Writing Final Classes and Methods&lt;/a&gt; &lt;a href=&quot;#fnref:java-doc&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:analysis&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.javaspecialists.eu/archive/Issue210-Calling-Methods-from-a-Constructor.html&quot;&gt;Calling Methods from a Constructor&lt;/a&gt; &lt;a href=&quot;#fnref:analysis&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:spring-lifecycle&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/6.0/core/beans/factory-nature.html#beans-factory-lifecycle&quot;&gt;Customizing the Nature of a Bean&lt;/a&gt; &lt;a href=&quot;#fnref:spring-lifecycle&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>christophHellmich</name></author><summary type="html">The article explains the new linter rule `this-escape` in JDK 21. We show why it was introduced and how to stop receiving this warning.</summary></entry><entry xml:lang="de"><title type="html">Behandlung der this-escape-Warnung in JDK 21</title><link href="https://blog.cronn.de/de/java/2024/07/11/this-escape.html" rel="alternate" type="text/html" title="Behandlung der this-escape-Warnung in JDK 21" /><published>2024-07-11T00:00:00-05:00</published><updated>2024-07-11T00:00:00-05:00</updated><id>https://blog.cronn.de/de/java/2024/07/11/this-escape</id><content type="html" xml:base="https://blog.cronn.de/de/java/2024/07/11/this-escape.html">&lt;p&gt;In der JDK Version 21 wurde der Java Linter um eine neue Regel erweitert. Nach der ist es nicht erlaubt, im Konstruktor einer Klasse eine überschreibbare Methode aufzurufen&lt;sup id=&quot;fnref:jdk-bug&quot;&gt;&lt;a href=&quot;#fn:jdk-bug&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Wird die Regel missachtet und der Java Code mit dem Flag &lt;span style=&quot;white-space: pre;&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;-Xlint:all&lt;/code&gt;&lt;/span&gt; oder &lt;span style=&quot;white-space: pre;&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;-Xlint:this-escape&lt;/code&gt;&lt;/span&gt; kompiliert, führt dies zur folgenden &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt; Warnung:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;warning: [this-escape] possible 'this' escape before subclass is fully initialized
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In diesem Beitrag wollen wir kurz betrachten, wieso im Konstruktor keine überschreibbaren Methoden aufgerufen werden sollten. Die darauffolgenden drei Abschnitte zeigen Verfahren, die für das Auflösen der Warnung in unseren Projekten angewandt wurden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hier kommt ihr gleich zu den drei Lösungswegen:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;#Verwendung der Schlüsselwörter `final`, `private` oder `static`&quot;&gt;Verwendung der Schlüsselwörter &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; oder &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;#Verwendung der Annotation `@PostConstruct`&quot;&gt;Verwendung der Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;#Überarbeiten des Klassendesigns&quot;&gt;Überarbeiten des Klassendesigns&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;hintergrund&quot;&gt;Hintergrund&lt;/h3&gt;
&lt;p&gt;Die Erweiterung des Java Linters in JDK 21 um die neue Regel ist eine gute Verbesserung, da sie hilft, Code-Smell zu verhindern. Keine überschreibbaren Methoden aus dem Konstruktor aufzurufen, wird seit Langem empfohlen&lt;sup id=&quot;fnref:effective-java&quot;&gt;&lt;a href=&quot;#fn:effective-java&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:java-doc&quot;&gt;&lt;a href=&quot;#fn:java-doc&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. Wie aber eine Analyse&lt;sup id=&quot;fnref:analyse&quot;&gt;&lt;a href=&quot;#fn:analyse&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; einiger bekannter Open-Source-Projekte zeigt, gibt es im Code dennoch immer wieder Stellen, an denen die Empfehlung missachtet oder vergessen wurde. Auch bei unseren Projekten konnte der Java Linter nach dem Upgrade auf JDK 21 vereinzelt Stellen finden, die nicht der Empfehlung folgten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Es wird im Folgenden vorausgesetzt, dass der abgebildete Code immer mit dem Flag &lt;span style=&quot;white-space: pre;&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;-Xlint:all&lt;/code&gt;&lt;/span&gt; kompiliert wird, auch wenn dies nicht extra angegeben wurde. Abrufbar ist der vollständige Code im dafür bereitgestellten &lt;a href=&quot;https://github.com/cronn/this-escape-blog-post-example/&quot;&gt;GitHub-Repository&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;origin-story&quot;&gt;Origin Story&lt;/h3&gt;
&lt;p&gt;Nachfolgend soll die Rationale für die &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung anhand eines Beispiels erläutert werden. Dazu schauen wir uns als Beispiel die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; an, siehe unten. Die Klasse hat eine Instanzvariable &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; und eine öffentliche nicht finale Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt;. Im Konstruktor der Klasse wird die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; aufgerufen. Der Code lässt sich mit JDK 17 problemlos kompilieren, aber beim Kompilieren mit JDK 21 gibt der Java Linter eine &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung aus.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;stranger&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Calls overrideable method, causes this-escape warning&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; für sich ist unproblematisch, aber sobald die Klasse erweitert wird, kann es zu schwer auffindbaren Fehlern führen. Davor warnt der Java Linter mit der &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung. Um einen Fehler provozieren zu können, erstellen wir zusätzlich die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt;, die die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; erweitert. Die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; fügt eine weitere Instanzvariable &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt; hinzu und überschreibt die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Musician&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;triangle&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I heard you play &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;. Awesome!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Was wird nun ausgegeben, wenn ein neues Objekt &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; mit der Anweisung &lt;code class=&quot;highlighter-rouge&quot;&gt;new Musician(&quot;Jimi&quot;, &quot;guitar&quot;)&lt;/code&gt; erstellt wird? Beim Erstellen einer Instanz von &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; wird im Konstruktor von &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician &lt;/code&gt; der Konstruktor von &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; aufgerufen. Im Konstruktor von &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; erfolgt die Initialisierung der Instanzvariable &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; und dann der Aufruf der Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt;. Anschließend wird im Konstruktor der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician &lt;/code&gt; die Variable &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt; initialisiert. Die Anweisung führt entsprechend zu der folgenden Ausgabe:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hello Jimi!
I heard you play null. Awesome!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Die überschriebene Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; wird aus &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; aufgerufen, noch bevor &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; vollständig instanziiert wurde. Dies führt dazu, dass für &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt; der Wert &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt; ausgegeben wird, obwohl &lt;code class=&quot;highlighter-rouge&quot;&gt;instrument&lt;/code&gt; nach Instanziierung des Objekts &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; nie den Wert &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt; haben kann. Die Ursache für die fehlerhafte Ausgabe ist in dem Beispiel schnell ersichtlich. Dennoch zeigt es, dass eine Klasse keine überschreibbaren Methoden der eigenen Klasse im Konstruktor aufrufen sollte, da die Klasse nicht sicherstellen kann, dass sie sich beim Aufrufen der Methode in einem konsistenten Zustand befindet. Demnach sollte die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; also nicht gleichzeitig überschreibbar sein und vom Konstruktor aufgerufen werden.&lt;/p&gt;

&lt;p&gt;Zu beachten ist, dass der Fehler in diesem Beispiel offensichtlich sein mag. Wir haben hier ein einfaches Beispiel betrachtet, um den Sachverhalt zu erläutern. In der Praxis kann der Fehler in einer umfangreichen Klasse innerhalb einer komplexen Klassenhierarchie mit weiteren Vererbungen und Verschachtelungen in Zusammenhang mit Nebenläufigkeit erheblich schwieriger zu lokalisieren sein.&lt;/p&gt;

&lt;h3 id=&quot;drei-lösungswege&quot;&gt;Drei Lösungswege&lt;/h3&gt;
&lt;p&gt;In den folgenden drei Abschnitten werden Möglichkeiten vorgestellt, wie das Aufrufen einer überschreibbaren Methode aus dem Konstruktor verhindert oder umgangen werden kann.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;Verwendung der Schlüsselwörter `final`, `private` oder `static`&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;verwendung-der-schlüsselwörter-final-private-oder-static&quot;&gt;Verwendung der Schlüsselwörter &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; oder &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Die direkteste Möglichkeit, die &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung zu verhindern, ist das Überschreiben aller vom Konstruktor aufgerufenen Methoden zu verbieten. Dies ist in Java mit den Schlüsselwörtern &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; und &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt; erreichbar. Wird eine Klasse als &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; deklariert, ist es nicht mehr möglich, diese zu erweitern. Dementsprechend ist auch keiner ihrer Methoden überschreibbar. Die Deklaration einer Methode als &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; oder &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt; sorgt dafür, dass nur die Methode nicht überschrieben werden kann.&lt;/p&gt;

&lt;p&gt;Die fehlerhafte Ausgabe der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; und &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; aus dem letzten Abschnitt können wir mithilfe der Schlüsselwörter auf unterschiedliche Weise beheben. Im Folgenden wird zunächst die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; von &lt;code class=&quot;highlighter-rouge&quot;&gt;Person&lt;/code&gt; als &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; deklariert, um den Java Linter zufriedenzustellen.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;stranger&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Method is now final&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; kann nun nicht mehr die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;greet()&lt;/code&gt; überschreiben. Stattdessen wird in der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; eine eigene Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;printInstrument()&lt;/code&gt; definiert, die für die Ausgabe des Instruments verantwortlich ist. In dem Beispiel wird davon ausgegangen, dass Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;Musician&lt;/code&gt; von keiner anderen Klasse erweitert werden soll, daher fügen wir das Schlüsselwort &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; zu der Deklaration der Klasse hinzu. Andernfalls würde uns der Java Linter auch hier eine &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;- Warnung ausgeben.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Class is now final&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Musician&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNullElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;triangle&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printInstrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;printInstrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I heard you play &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;. Awesome!&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nach den Änderungen führt die Anweisung &lt;code class=&quot;highlighter-rouge&quot;&gt;new Musician(&quot;Jimi&quot;, &quot;guitar&quot;)&lt;/code&gt; zu der folgenden Ausgabe:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hello Jimi!
I heard you play guitar! Awesome!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nicht immer ist es möglich, eine Klasse als &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; oder eine Methode als &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; oder &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt; zu deklarieren. Falls die Klasse von einem Dependency Injection Framework, wie Spring oder Quarkus, verwaltet wird, kann der Aufruf von überschreibbaren Methoden aus dem Konstruktor meist auch auf eine andere Weise umgangen werden. Diese schauen wir uns im nächsten Abschnitt an.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;Verwendung der Annotation `@PostConstruct`&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;verwendung-der-annotation-postconstruct&quot;&gt;Verwendung der Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;In den nachfolgenden Beispielen verwenden wir Spring. Das Verfahren ist aber genauso für andere Dependency Injection Frameworks anwendbar, welche die &lt;em&gt;Jakarta Contexts and Dependency Injection&lt;/em&gt; Spezifikation bzw. die &lt;em&gt;Jakarta Annotations&lt;/em&gt; Spezifikation implementieren. Teil der &lt;em&gt;Jakarta Annotations&lt;/em&gt; Spezifikation ist die Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostContruct&lt;/code&gt;, welche wesentlich für das hier vorgestellte Verfahren ist. Mithilfe der Annotation können wir uns in den Lebenszyklus einer von Spring verwalteten Bean einklinken. Wie es der Name bereits vermuten lässt, ist es in dem Fall von &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; der Zeitpunkt, nachdem der Konstruktor ausgeführt und die Bean vollständig initialisiert wurde. Dies ermöglicht es, den Aufruf einer überschreibbaren Methode aus dem Konstruktor heraus an eine sichere Stelle zu verschieben. Spring bietet noch andere Möglichkeiten, eigenen Code in den Lebenszyklus einer Bean einzufügen, aber die Verwendung von &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; ist die empfohlene&lt;sup id=&quot;fnref:spring-lifecycle&quot;&gt;&lt;a href=&quot;#fn:spring-lifecycle&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, weshalb hier nur auf diese eingegangen wird.&lt;/p&gt;

&lt;p&gt;Wir erweitern das Beispiel aus dem vorherigen Abschnitt, um die Verwendung von &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; veranschaulichen zu können. In dem vorherigen Beispiel wurde jedem Musiker das Instrument &lt;em&gt;triangle&lt;/em&gt; zugeordnet, wenn kein Instrument angegeben wurde. Das wollen wir nachfolgend ein wenig optimieren, indem wir ermöglichen, eine externe Ressource anzubinden. Diese soll ein Mapping zwischen bekannten Musikern, repräsentiert durch ihren Namen, und ihrem Instrument bereitstellen. Für einen schnelleren Zugriff ist das Mapping in einem Cache zu speichern. Die Verwendung des Caches ist im folgenden Listing schematisch dargestellt:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Get name of a musician from somewhere&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getInstrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Get instrument from somewhere&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/*
         musicianInstrumentCache contains a mapping of the form:
         Jimi -&amp;gt; guitar
         Miles -&amp;gt; trumpet
         Ludwig -&amp;gt; piano
         ...
    */&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;musicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;musician&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Musician&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Für die Umsetzung erstellen wir zwei Klassen. Die abstrakte Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; beinhaltet einen einfachen Cache, der als eine &lt;code class=&quot;highlighter-rouge&quot;&gt;Map&lt;/code&gt; mit der Abbildung Musikername ⟼ Instrument realisiert wurde, und ruft im Konstruktor die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; auf. Die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; soll von den Spezialisierungen von &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; (s.u.) dazu verwendet werden, eine externe Ressource einzulesen und den Cache zu aktualisieren. Für die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; gilt:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Der Aufruf der Methode soll es anderen Klassen ermöglichen, den Cache zur Laufzeit zu aktualisieren. Daher soll die Methode öffentlich sein.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Für unterschiedliche Typen von Ressourcen, wie externe Dateien, Datenbanken usw., sollen verschiedene Spezialisierungen von &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; erstellt werden können, welche die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; entsprechend der eingesetzten Ressource überschreiben. Daher soll die Methode abstrakt sein und kann nicht als &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; oder &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt; deklariert werden.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Die Implementierung der abstrakten Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; ist nachfolgend angegeben. Der Linter gibt eine &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung beim Kompilieren der Klasse aus, da im Konstruktor die abstrakte Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; aufgerufen wird.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MusicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MusicianInstrumentCache.init()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Calls overrideable method, causes this-escape warning&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Should be public and abstract&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Bevor wir uns dem Problem widmen, betrachten wir eine Spezialisierung der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. Die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; zeigt eine mögliche Spezialisierung von &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. Die Klasse soll das Mapping über den von Spring injizierten &lt;code class=&quot;highlighter-rouge&quot;&gt;ResourceLoader&lt;/code&gt; aus einer Datei einlesen und anschließend in den Cache schreiben. Um das Beispiel kurzzuhalten, wurde das Einlesen der Datei und das Schreiben in den Cache in dem Code nur angedeutet.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileBasedMusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;classpath:mapping.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FileBasedMusicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FileBasedMusicianInstrumentCache.init()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resourceLoader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FileBasedMusicianInstrumentCache.updateCache()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Logic for importing mapping and adding it to the cache. Briefly,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// represented by the following lines without exception handling:&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Resource&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContentAsString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StandardCharsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UTF_8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\n&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// getter and setter&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Zu beachten ist, dass der &lt;code class=&quot;highlighter-rouge&quot;&gt;resourceLoader&lt;/code&gt; noch nicht gesetzt wurde, wenn im Konstruktor der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; aufgerufen wird. Das liegt daran, dass wie im Abschnitt &lt;em&gt;Origin Story&lt;/em&gt; beschrieben im Konstruktor der erweiternden Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; als erste Anweisung der Konstruktor der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; aufgerufen wird, selbst wenn der Aufruf über &lt;code class=&quot;highlighter-rouge&quot;&gt;super()&lt;/code&gt; nicht explizit im Java Code angegeben wurde. Dies ist auch in der Ausgabe ersichtlich, bei der &lt;em&gt;FileBasedMusicianInstrumentCache.updateCache()&lt;/em&gt; vor &lt;em&gt;FileBasedMusicianInstrumentCache.init()&lt;/em&gt; in die Konsole geschrieben wird:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MusicianInstrumentCache.init()
FileBasedMusicianInstrumentCache.updateCache()
FileBasedMusicianInstrumentCache.init()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Glücklicherweise lässt sich der Fehler und die &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung mit der Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; ohne größere Anpassungen beheben, sodass nicht mehr die überschreibbare Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; aufgerufen wird, bevor das Objekt vollständig initialisiert wurde. Es genügt, die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; in der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; mit &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; zu annotieren. Der Aufruf der Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; kann aus dem Konstruktor entfernt werden, da Spring jetzt für den Aufruf verantwortlich ist. Die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; kann unverändert bleiben, da Spring prüft, ob in einer Superklasse eine Methode mit &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; annotiert ist und das Verhalten für die Unterklassen übernimmt.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MusicianInstrumentCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MusicianInstrumentCache.init()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Remove importMapping() method call here&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@PostConstruct&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Add annotation&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateCache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Beim Starten der Anwendung wird nun zunächst beim Initialisieren der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; wie bisher der Konstruktor &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; aufgerufen, aber in dem Konstruktor wird nicht mehr die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; aufgerufen, sondern stattdessen zunächst der Konstruktor von &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; abgeschlossen. Erst nachdem &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; vollständig konstruiert wurde, ruft Spring die mit &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; annotierte Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; auf. Dies führt zu der folgenden Ausgabe:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MusicianInstrumentCache.init()
FileBasedMusicianInstrumentCache.init()
FileBasedMusicianInstrumentCache.updateCache()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Das Vorgehen mit &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; ermöglicht die Verwendung von überschreibbarer Methode an die Erstellung des Objekts zu koppeln, ohne die Probleme, die beim Aufruf aus dem Konstruktor heraus resultieren können. Dies setzt allerdings voraus, dass ein Dependency Injection Framework verwendet wird, welches die Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; oder vergleichbares unterstützt.&lt;/p&gt;

&lt;p&gt;In den vorherigen beiden Abschnitten wurden Vorgehen beschrieben, um den Linter durch kleine Kniffe wieder zufriedenzustellen. In dem nächsten Abschnitt betrachten wir noch eine andere Möglichkeit, mit der Warnung umzugehen.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;Überarbeiten des Klassendesigns&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;überarbeiten-des-klassendesigns&quot;&gt;Überarbeiten des Klassendesigns&lt;/h4&gt;
&lt;p&gt;Manchmal kann die &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung auch als Anregung dienen, das Klassendesign erneut zu evaluieren. Je nach Ergebnis der Evaluation können die durchzuführenden Änderungen einen größeren Einfluss auf die Struktur des Codes haben, als es bei den beiden anderen Verfahren der Fall war. Wir greifen das Beispiel aus dem vorherigen Abschnitt auf, um aufzuzeigen, wie eine Anpassung des Klassendesigns aussehen könnte.&lt;/p&gt;

&lt;p&gt;In dem letzten Abschnitt wurden die beiden Klassen &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; und &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentCache&lt;/code&gt; erstellt, wobei letztere die erstere erweiterte. Durch die Vererbung musste die Methode &lt;code class=&quot;highlighter-rouge&quot;&gt;updateCache()&lt;/code&gt; öffentlich und überschreibbar sein, was schließlich zu der &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung geführt hat. Das Klassendesign soll im Folgenden eine Komposition statt einer Vererbung verwenden.&lt;/p&gt;

&lt;p&gt;Die Funktionalität der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; wird dazu aufgeteilt. Das Verwalten des Cache wird weiterhin Aufgabe der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; bleiben. Das Einlesen einer externen Ressource wird in die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt; ausgelagert. Die Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt; erhält außerdem eine Referenz auf eine Instanz der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. Unten ist das alte, auf Vererbung basierende Klassendesign, dem neuen Klassendesign in einem UML-Klassendiagramm gegenübergestellt.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Vererbung                             Komposition
=========                             ===========
┌────────────────────────────────┐   ┌────────────────────────────────────────┐
│           &amp;lt;abstract&amp;gt;           │   │                                        │
│    MusicianInstrumentCache     │   │        MusicianInstrumentCache         │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│#cache:Map&amp;lt;String,String&amp;gt;       │   │-cache:Map&amp;lt;String,String&amp;gt;               │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│+importMapping():void &amp;lt;abstract&amp;gt;│   │~put(name:String,instrument:String):void│
│+getInstrumentFor(String):String│   │+getInstrumentFor(String):String        │
└────────────────────────────────┘   └────────────────────────────────────────┘
                 ▲                                       ^
                 │                                       │
                 │                                       │ -cache
                 │                                       │
┌────────────────┴───────────────┐   ┌───────────────────┴────────────────────┐
│FileBasedMusicianInstrumentCache│   │  FileBasedMusicianInstrumentImporter   │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│-resourceLoader:ResourceLoader  │   │-resourceLoader:ResourceLoader          │
├────────────────────────────────┤   ├────────────────────────────────────────┤
│+importMapping():void           │   │+importMapping():void                   │
└────────────────────────────────┘   └────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Das nachfolgende Listing zeigt den Code der neuen Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt;. Die Klasse besitzt zwei Methoden, jeweils eine für das Auslesen und Schreiben des Cache. &lt;code class=&quot;highlighter-rouge&quot;&gt;MusicianInstrumentCache&lt;/code&gt; wurde mit &lt;code class=&quot;highlighter-rouge&quot;&gt;@Component&lt;/code&gt; annotiert, da es vom Dependency Injection Framework verwaltet und in &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt; injiziert werden soll.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getInstrumentFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Der Code der Klasse &lt;code class=&quot;highlighter-rouge&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/code&gt; ist im folgenden Listing dargestellt. Die Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt; wird nicht mehr benötigt. Es reicht nun aus, die Klassen als &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt; zu deklarieren.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;classpath:mapping.csv&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FileBasedMusicianInstrumentImporter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MusicianInstrumentCache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                                               &lt;span class=&quot;nc&quot;&gt;ResourceLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resourceLoader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;importMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;importMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Logic for importing mapping and adding it to the cache. Briefly,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// represented by the following lines without exception handling:&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Resource&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mappingResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContentAsString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StandardCharsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UTF_8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\n&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// getter and setter&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Das Beispiel soll einen Eindruck liefern, wie eine Überarbeitung des Klassendesigns aussehen könnte. Dies muss jedoch nicht immer eine Umstellung von Vererbung nach Komposition beinhalten. Möglich wäre etwa auch das Extrahieren/Verschieben von Methoden oder die Anwendung eines Erzeugermusters, um den Aufruf einer überschreibbaren Methode aus dem Konstruktor aufzulösen.&lt;/p&gt;

&lt;p&gt;An dieser Stelle haben wir alle Verfahren betrachtet, die beim Upgrade unseres Projekts angewendet wurden. Der nächste Abschnitt fasst noch mal die wesentlichen Punkte dieses Beitrags zusammen.&lt;/p&gt;

&lt;h3 id=&quot;zusammenfassung&quot;&gt;Zusammenfassung&lt;/h3&gt;
&lt;p&gt;In diesem Beitrag wurde die Motivation hinter der &lt;code class=&quot;highlighter-rouge&quot;&gt;this-escape&lt;/code&gt;-Warnung des Java Linters beschrieben und drei Möglichkeiten gezeigt, die Warnungen zu verhindern. Die Möglichkeiten sind nachfolgend aufgelistet:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Verwendung der Schlüsselwörter &lt;code class=&quot;highlighter-rouge&quot;&gt;final&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;private&lt;/code&gt; oder &lt;code class=&quot;highlighter-rouge&quot;&gt;static&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Verwendung der Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@PostConstruct&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Überarbeitung des Klassendesigns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nicht immer ist jedes Verfahren anwendbar. Manchmal ist auch eine Kombination verschiedener Verfahren nötig, um die Warnung zu beheben. Welcher Umgang mit der Warnung am besten geeignet ist, muss von Fall zu Fall entschieden werden. Meistens sollte das erste oder zweite Verfahren jedoch ausreichen. Die Anwendung des zweiten Verfahrens setzt allerdings voraus, dass die betroffene Klasse durch ein Dependency Injection Framework wie Spring oder Quarkus verwaltet wird. Das Überarbeiten des Klassendesigns sollte immer zum Erfolg führen, kann allerdings auch am meisten Zeit in Anspruch nehmen.&lt;/p&gt;

&lt;h3 id=&quot;referenzen&quot;&gt;Referenzen&lt;/h3&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:jdk-bug&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://bugs.openjdk.org/browse/JDK-8015831&quot;&gt;Add lint check for calling overridable methods from a constructor&lt;/a&gt; &lt;a href=&quot;#fnref:jdk-bug&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:effective-java&quot;&gt;
      &lt;p&gt;Joshua Bloch. 2001. Effective Java programming language guide. Sun Microsystems, Inc., USA. &lt;a href=&quot;#fnref:effective-java&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:java-doc&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/IandI/final.html&quot;&gt;Writing Final Classes and Methods&lt;/a&gt; &lt;a href=&quot;#fnref:java-doc&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:analyse&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.javaspecialists.eu/archive/Issue210-Calling-Methods-from-a-Constructor.html&quot;&gt;Calling Methods from a Constructor&lt;/a&gt; &lt;a href=&quot;#fnref:analyse&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:spring-lifecycle&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/6.0/core/beans/factory-nature.html#beans-factory-lifecycle&quot;&gt;Customizing the Nature of a Bean&lt;/a&gt; &lt;a href=&quot;#fnref:spring-lifecycle&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>christophHellmich</name></author><summary type="html">Der Artikel erklärt die neue Linter-Regel 'this-escape' in JDK 21. Wir zeigen, warum sie eingeführt wurde und wie man die Warnung umgeht.</summary></entry><entry xml:lang="en"><title type="html">Time-Travelling through Data Realms with Auditing</title><link href="https://blog.cronn.de/en/databases/auditing/2024/03/26/time-travelling-auditing.html" rel="alternate" type="text/html" title="Time-Travelling through Data Realms with Auditing" /><published>2024-03-26T00:00:00-05:00</published><updated>2024-03-26T00:00:00-05:00</updated><id>https://blog.cronn.de/en/databases/auditing/2024/03/26/time-travelling-auditing</id><content type="html" xml:base="https://blog.cronn.de/en/databases/auditing/2024/03/26/time-travelling-auditing.html">&lt;h3 id=&quot;guardian-of-data-history&quot;&gt;Guardian of data history&lt;/h3&gt;
&lt;p&gt;Auditing plays a significant role in the realm of data management. It serves as the guardian of data history, ensuring that every alteration to data is not only tracked, but also logged comprehensively. Essentially, auditing offers us a unique time-traveling mechanism, allowing us to traverse the history of our data, discover who made which changes, and understand the when and why behind those alterations.
As developers, it is vital to consider efficient auditing methods for specific applications and use-cases. In this article, I will lay out the differences between the Java-based auditing tools &lt;em&gt;Hibernate Envers&lt;/em&gt;, &lt;em&gt;JPA Data&lt;/em&gt; and database trigger-based auditing. Using a simple Java application with &lt;em&gt;Spring Boot&lt;/em&gt;, &lt;em&gt;Spring Security&lt;/em&gt;, &lt;em&gt;JPA Data&lt;/em&gt; and &lt;em&gt;Hibernate ORM&lt;/em&gt; as an example, we will explore which approach is most suitable for various situations and requirements.&lt;/p&gt;

&lt;h3 id=&quot;first-the-basics-change-data-capture&quot;&gt;First, the basics: Change Data Capture&lt;/h3&gt;
&lt;p&gt;Change Data Capture (CDC) describes a process in which changes made to data in a database are propagated in real-time to a downstream system. This builds the basis for any auditing system. Any auditing process needs to be alerted that the audited data has been changed. This change could be a database insert, or a modify or delete operation. The existence of this change is then either preserved in some way, or a trigger event could be initiated.&lt;/p&gt;

&lt;h3 id=&quot;stay-in-the-java-world-hibernate-envers&quot;&gt;Stay in the Java world: Hibernate Envers&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Hibernate Envers&lt;/em&gt; presents a CDC solution that simplifies auditing within Java applications. What sets it apart is its seamless integration with &lt;em&gt;Hibernate ORM&lt;/em&gt;, ensuring that you can stay firmly within the Java ecosystem. It works with one global &lt;code class=&quot;highlighter-rouge&quot;&gt;REVINFO&lt;/code&gt; table as well as a table for each entity. To audit an entity, it is sufficient to annotate it with the &lt;code class=&quot;highlighter-rouge&quot;&gt;@Audited&lt;/code&gt; annotation.
While Envers uses a deny-listing instead of an allow-listing approach, keep in mind that each property is then automatically audited if not explicitly excluded. You will remember this once your database runs out of space because you forgot to explicitly exclude big files from auditing. Let us look at a simple example: imagine we have an Entity called &lt;code class=&quot;highlighter-rouge&quot;&gt;dog&lt;/code&gt; with a few simple properties like name and height, and we mark it to be audited.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Audited&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@Id&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;//Getters and setters&lt;/span&gt;

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

&lt;p&gt;From this, hibernate will generate three tables: the known entity table, the &lt;code class=&quot;highlighter-rouge&quot;&gt;REVINFO&lt;/code&gt; table, and the entity audit table (see picture). The &lt;code class=&quot;highlighter-rouge&quot;&gt;REVINFO&lt;/code&gt; table consists of a revision ID and a timestamp. The revision ID included in the REVINFO table is pointing to the revision ID in the entity audit table. The audit table additionally includes the entity properties and a &lt;code class=&quot;highlighter-rouge&quot;&gt;REVTYPE&lt;/code&gt; column that states the type of the occurred modification. That means that the auditing table has a history of every revision of an entity. That comes in handy if one wants to fetch the state of an entity at any given point in time, for example to diff it with the current state.
Envers provides numerous ways to fulfill your auditing desires by offering a ton of configuration possibilities: from excluding single properties from being audited, to customizing the auditing tables itself. Read more about that in its &lt;a href=&quot;https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#envers&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img data-src=&quot;/img/posts/time-traveling-auditing/2024_02_21_angelina_blog.jpg&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Three tables generated by Hibernate&quot; /&gt;
    &lt;figcaption class=&quot;long-fig-caption&quot;&gt; 
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To retrieve the audit data, Envers offers a &lt;code class=&quot;highlighter-rouge&quot;&gt;AuditReaderFactory&lt;/code&gt; which queries all entity state snapshots. For our example &lt;code class=&quot;highlighter-rouge&quot;&gt;dog&lt;/code&gt; entity with an object instance with ID &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;, this would look like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snapshots&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuditReaderFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forRevisionsOfEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuditEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;pros&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Stays in the Java World:&lt;/strong&gt; Hibernate Envers offers a smooth transition into the auditing world, keeping your entire process within the Java ecosystem. This is particularly advantageous if your application primarily revolves around Java.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Automatic Table Generation:&lt;/strong&gt; Envers streamlines the auditing setup by automatically generating auditing tables, even for databases like PostgreSQL. Tools like LiquiBase can further simplify this process, making it a breeze to configure.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Database-Agnostic:&lt;/strong&gt; One of the standout features of Envers is its database-agnostic nature. It doesn’t confine you to a particular type of database, ensuring flexibility across various database systems, whether you are working with e.g., PostgreSQL or MariaDB.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Revision at Point in Time:&lt;/strong&gt; Envers empowers you to access data as it existed at a specific revision, offering granular insights for historical data analysis or diffing.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Performance Overhead:&lt;/strong&gt; Envers introduces an additional layer of SQL operations for each transaction, including insertions, updates, and deletions. While these operations are essential for comprehensive auditing, they may potentially impact the overall performance due to their synchronous nature.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Limited Data Source:&lt;/strong&gt; Envers primarily captures changes initiated within your application. This means that changes made through SQL consoles or other external applications might go unnoticed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Lack of Bulk Support:&lt;/strong&gt; Envers does not provide support for bulk updates or deletions, which can be a limitation in use cases where such operations are a common occurrence.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;break-out-of-your-java-comfort-zone-database-triggers&quot;&gt;Break out of your java comfort zone: Database triggers&lt;/h3&gt;
&lt;p&gt;If you are not afraid of leaving the cozy safety provided by Java abstraction layers, want to improve the performance of your auditing solution, or desire more customization, trigger-based auditing could be the right choice for you.
Let’s look at how we can transform our &lt;code class=&quot;highlighter-rouge&quot;&gt;dog&lt;/code&gt; example to use trigger-based auditing. In a first step, we want to record any type of change in a separate &lt;code class=&quot;highlighter-rouge&quot;&gt;dog_audit_log&lt;/code&gt; table which contains a &lt;code class=&quot;highlighter-rouge&quot;&gt;new_value&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;old_value&lt;/code&gt; column with type &lt;code class=&quot;highlighter-rouge&quot;&gt;jsonb&lt;/code&gt;. We use &lt;code class=&quot;highlighter-rouge&quot;&gt;jsonb&lt;/code&gt; as the column type here for simplicity and durability reasons: if a column is added or removed from the dog entity, it will not affect our audit log table.&lt;/p&gt;
&lt;figure&gt;
    &lt;img data-src=&quot;/img/posts/time-traveling-auditing/2024_02_21_angelina_blog2.jpg&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Two tables, the left one depicts the entity 'dog', the right one the audit log&quot; /&gt;
    &lt;figcaption class=&quot;long-fig-caption&quot;&gt; 
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In order to achieve this, we need to implement a function in &lt;em&gt;PostgreSQL&lt;/em&gt; database which will transfer the committed changes. Additionally, we need to also implement a trigger which will execute this function if a &lt;code class=&quot;highlighter-rouge&quot;&gt;INSERT&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;DELETE&lt;/code&gt; statement is committed.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog_audit_trigger&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;AFTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EACH&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog_audit_trigger_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are also more automated solutions available that use database triggers, e.g., &lt;em&gt;Debezium&lt;/em&gt;. It is &lt;em&gt;Kafka&lt;/em&gt; based and extracts the CDC events from the binary log of the database.&lt;/p&gt;

&lt;p&gt;To retrieve the audit data, one must “manually” query the &lt;code class=&quot;highlighter-rouge&quot;&gt;dog_audit_log&lt;/code&gt; and join it with the &lt;code class=&quot;highlighter-rouge&quot;&gt;dog&lt;/code&gt; table.&lt;/p&gt;

&lt;h4 id=&quot;pros-1&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Decoupled Architecture:&lt;/strong&gt; One of the most significant advantages of trigger-based auditing is its decoupled architecture. It operates independently of your application, providing a non-intrusive auditing solution.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Comprehensive Auditing:&lt;/strong&gt; It captures all changes, not just those initiated within your application. This breadth makes it suitable for auditing data from various sources, ensuring that no change goes unnoticed.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons-1&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Not Coupled with Hibernate:&lt;/strong&gt; Trigger-based auditing is not tightly integrated with Hibernate, which may make it a less convenient choice if your application heavily relies on Hibernate for data management.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Cascading Trigger Hell:&lt;/strong&gt; Be aware of possible side effects of other database operations that could initiate cascading triggers.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Metadata Retrieval Challenges:&lt;/strong&gt; Extracting metadata can be more challenging. This may require additional effort and customization to achieve the desired results.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;keep-it-simple-jpa-data&quot;&gt;Keep it simple: JPA Data&lt;/h3&gt;
&lt;p&gt;For basic auditing requirements, &lt;em&gt;JPA Data&lt;/em&gt; offers a straightforward solution. It enables you to track when an entity was created or modified, providing basic auditing capabilities without the intricacies associated with &lt;em&gt;Hibernate Envers&lt;/em&gt; or database triggers. To achieve this, we can use simple annotation provided by JPA Data.
Additionally, we need to add the &lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableJpaAuditing&lt;/code&gt; annotation to our configuration class. Let’s look at our example &lt;code class=&quot;highlighter-rouge&quot;&gt;dog&lt;/code&gt; entity again:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EntityListeners&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuditingEntityListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@Id&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@CreatedDate&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@CreatedBy&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@LastModifiedDate&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedAt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@LastModifiedBy&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;//Getters and setters&lt;/span&gt;

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

&lt;p&gt;We simply implement four more attributes in our entity which track when and by whom it was created or modified. In contrast to Envers and trigger-based solutions, these attributes are not saved in a separate table and do not include a snapshot of the previous state. That means that JPA Data does not keep a history of revisions of your entity anywhere, only when and by whom the current version was created or &lt;em&gt;last&lt;/em&gt; edited.
While this solution does not offer the level of customization provided by hibernate, it is possible to extend or modify its capabilities by, e.g., modifying the used &lt;code class=&quot;highlighter-rouge&quot;&gt;EntityListener&lt;/code&gt;.
Since we do not save snapshots of an entity, we can only query the current entity object, including when and by whom it was modified last or created.&lt;/p&gt;

&lt;h4 id=&quot;pros-2&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Simplicity:&lt;/strong&gt; JPA Data shines in its simplicity. It’s easy to set up and use, making it an excellent fit for projects with basic auditing requirements.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Due to its lightweight nature, JPA Data introduces minimal performance overhead, ensuring that your application’s performance remains largely unaffected.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons-2&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Limited Auditing Features:&lt;/strong&gt; JPA Data, while simple and lightweight, offers only basic auditing capabilities. It is suitable for straightforward use cases but falls short when it comes to more advanced auditing requirements.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Inability to Capture Delete Operations:&lt;/strong&gt; JPA Data does not capture delete operations, limiting its effectiveness in comprehensive auditing scenarios where tracking deletions is essential.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;In conclusion, selecting the right auditing approach depends, as always, on your project’s specific requirements and priorities.&lt;/p&gt;

&lt;p&gt;Hibernate Envers offers seamless integration, keeping you firmly in the Java world, but it comes with potential performance overhead.&lt;/p&gt;

&lt;p&gt;Trigger-based auditing is the go-to-choice for capturing changes from various sources, but its setup may be more complex, and it lacks tight integration with Hibernate.&lt;/p&gt;

&lt;p&gt;JPA Data is ideal for basic auditing needs, prioritizing simplicity and minimal performance impact. However, it may not suffice for advanced auditing scenarios.&lt;/p&gt;</content><author><name>angelinaMohr</name></author><summary type="html">Discover efficient auditing methods for tracking data history within database-driven applications, including JPA Data and Hibernate Envers.</summary></entry><entry xml:lang="de"><title type="html">Zeitreisen durch Datenwelten mit Auditing</title><link href="https://blog.cronn.de/de/datenbanken/auditing/2024/03/26/zeitreisen-auditing.html" rel="alternate" type="text/html" title="Zeitreisen durch Datenwelten mit Auditing" /><published>2024-03-26T00:00:00-05:00</published><updated>2024-03-26T00:00:00-05:00</updated><id>https://blog.cronn.de/de/datenbanken/auditing/2024/03/26/zeitreisen-auditing</id><content type="html" xml:base="https://blog.cronn.de/de/datenbanken/auditing/2024/03/26/zeitreisen-auditing.html">&lt;h3 id=&quot;hüter-der-datenhistorie&quot;&gt;Hüter der Datenhistorie&lt;/h3&gt;
&lt;p&gt;Audit-Verfahren sind im Bereich des Datenmanagements unverzichtbar. Sie fungieren als Hüter der Datenhistorie und gewährleisten, dass jede Änderung an Daten nicht nur nachverfolgt, sondern auch detailliert festgehalten wird. Audit-Methoden ermöglichen uns eine Art Zeitreise durch die Datenhistorie, um zu verstehen, wer, wann und welche Änderungen vorgenommen hat.&lt;/p&gt;

&lt;p&gt;Für Entwickler ist es daher wichtig, effektive Audit-Techniken für spezifischen Anwendungen und Anforderungen zu kennen. In diesem Artikel stelle ich die Unterschiede zwischen den Java-basierten Tools &lt;em&gt;Hibernate Envers&lt;/em&gt;, &lt;em&gt;JPA Data&lt;/em&gt; und dem Einsatz von Datenbank-Triggern für das Auditing vor. Anhand einer Beispielanwendung mit &lt;em&gt;Spring Boot&lt;/em&gt;, &lt;em&gt;Spring Security&lt;/em&gt;, &lt;em&gt;JPA Data&lt;/em&gt; und &lt;em&gt;Hibernate ORM&lt;/em&gt; untersuchen wir, welcher Ansatz für verschiedene Szenarien am besten geeignet ist.&lt;/p&gt;

&lt;h3 id=&quot;ein-blick-auf-die-grundlagen-change-data-capture&quot;&gt;Ein Blick auf die Grundlagen: Change Data Capture&lt;/h3&gt;
&lt;p&gt;Change Data Capture (CDC) ist ein Verfahren, das Datenänderungen in einer Datenbank in Echtzeit an ein nachgelagertes System übermittelt. Diese Technik ist das Fundament jedes Audit-Systems, denn jeder Audit-Prozess muss über Änderungen an den überwachten Daten informiert werden. Solche Änderungen können Einfüge-, Update- oder Löschoperationen in der Datenbank sein. Das Erkennen dieser Änderungen erfolgt entweder durch Aufzeichnung oder durch Auslösen eines Trigger-Ereignisses.&lt;/p&gt;

&lt;h3 id=&quot;in-der-java-welt-hibernate-envers&quot;&gt;In der Java-Welt: Hibernate Envers&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Hibernate Envers&lt;/em&gt; bietet eine CDC-Lösung speziell für Java-Anwendungen, die das Auditing vereinfacht. Die nahtlose Integration mit &lt;em&gt;Hibernate ORM&lt;/em&gt; ermöglicht es, innerhalb des Java-Ökosystems zu bleiben. Das System nutzt eine zentrale &lt;code class=&quot;highlighter-rouge&quot;&gt;REVINFO&lt;/code&gt;-Tabelle sowie spezifische Tabellen für jede Entität. Um eine Entität zu auditieren, genügt es, diese mit der Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@Audited&lt;/code&gt; zu versehen.&lt;/p&gt;

&lt;p&gt;Envers setzt auf einen Deny-Listing-Ansatz. Eigenschaften werden also standardmäßig auditiert – es sei denn, sie werden explizit ausgenommen. Das wird besonders dann deutlich, wenn die Datenbank an ihre Grenzen stößt, weil man vergessen hat, große Dateien vom Audit auszunehmen. Betrachten wir dazu ein einfaches Beispiel: Eine Entität namens &lt;code class=&quot;highlighter-rouge&quot;&gt;Dog&lt;/code&gt; mit Eigenschaften wie Name und Größe wird für das Auditing annotiert.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Audited&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@Id&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;//Getters and setters&lt;/span&gt;

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

&lt;p&gt;Daraus erzeugt Hibernate drei spezifische Tabellen, wie in der folgenden Abbildung dargestellt.
Zu diesen Tabellen zählen die Übersicht der auditierten Entitäten, die &lt;code class=&quot;highlighter-rouge&quot;&gt;REVINFO&lt;/code&gt;-Tabelle und die Tabelle für die Überprüfung der Entitäten selbst. Die REVINFO-Tabelle setzt sich aus der Revisions-ID und einem Zeitstempel zusammen. Diese Revisions-ID korrespondiert mit der in der Tabelle für die Entitätsprüfung, die neben der Revisions-ID und den Entitätseigenschaften auch eine Spalte &lt;code class=&quot;highlighter-rouge&quot;&gt;REVTYPE&lt;/code&gt; enthält. Diese Spalte kennzeichnet den Typ der durchgeführten Änderung, was bedeutet, dass die Audittabelle eine vollständige Historie jeder Revision einer Entität bewahrt. Das ist besonders nützlich, wenn man den Zustand einer Entität zu einem bestimmten Zeitpunkt rekonstruieren möchte, etwa um ihn mit dem aktuellen Zustand zu vergleichen.&lt;/p&gt;

&lt;p&gt;Envers bietet umfangreiche Konfigurationsmöglichkeiten, um sämtlichen Anforderungen an das Auditing gerecht zu werden. Das reicht vom Ausschluss bestimmter Eigenschaften von der Auditierung bis hin zur individuellen Anpassung der Auditing-Tabellen. Weitere Informationen dazu findet man in der offiziellen &lt;a href=&quot;https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#envers&quot;&gt;Dokumentation&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img data-src=&quot;/img/posts/time-traveling-auditing/2024_02_21_angelina_blog.jpg&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Drei von Hibernate erzeugte Tabellen&quot; /&gt;
    &lt;figcaption class=&quot;long-fig-caption&quot;&gt; 
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Um Audit-Daten abzurufen, bietet Envers die &lt;code class=&quot;highlighter-rouge&quot;&gt;AuditReaderFactory&lt;/code&gt; an, die es ermöglicht, alle Zustandsschnappschüsse einer Entität zu ermitteln. Für unser Beispiel mit der Entität &lt;code class=&quot;highlighter-rouge&quot;&gt;Dog&lt;/code&gt; und der Objektinstanz-ID „1“ würde das folgendermaßen aussehen:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snapshots&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuditReaderFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entityManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forRevisionsOfEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuditEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;vorteile&quot;&gt;Vorteile:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;In der Java-Welt:&lt;/strong&gt; Hibernate Envers ermöglicht einen reibungslosen Übergang zu Auditing-Systemen, alles innerhalb des Java-Ökosystems.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Automatische Tabellengenerierung:&lt;/strong&gt; Envers erleichtert die Einrichtung von Auditing-Tabellen durch automatische Generierung, auch für Datenbanken wie PostgreSQL. Werkzeuge wie LiquiBase können diesen Prozess noch weiter vereinfachen.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Datenbank-agnostisch:&lt;/strong&gt; Ein besonderer Vorteil von Envers ist seine Datenbank-Unabhängigkeit. Dadurch ist man ist nicht auf einen bestimmten Datenbanktyp festgelegt und kann flexibel mit verschiedenen Datenbanksystemen arbeiten, sei es PostgreSQL, MariaDB oder andere.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Revision zu einem bestimmten Zeitpunkt:&lt;/strong&gt; Mit Envers kann man auf Daten zugreifen, wie sie zu einem bestimmten Zeitpunkt vorlagen, und so detaillierte Einblicke in historische Daten oder für Vergleichsanalysen gewinnen.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;nachteile&quot;&gt;Nachteile:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Performance Overhead:&lt;/strong&gt; Envers fügt jeder Transaktion zusätzliche SQL-Operationen hinzu, einschließlich Einfügungen, Aktualisierungen und Löschvorgänge. Diese notwendigen Operationen können aufgrund ihrer synchronen Natur die Gesamtperformance beeinträchtigen.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Beschränkte Datenquelle:&lt;/strong&gt; Envers konzentriert sich hauptsächlich auf Änderungen, die innerhalb der Anwendung durchgeführt werden. Änderungen, die über SQL-Konsolen oder durch andere externe Anwendungen erfolgen, könnten daher übersehen werden.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Fehlende Unterstützung für Bulkoperationen:&lt;/strong&gt; Envers bietet keine spezielle Unterstützung für Bulkaktualisierungen oder -löschungen – in Szenarien, in denen solche Operationen regelmäßig anfallen, kann das eine Einschränkung darstellen.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;raus-aus-der-java-komfortzone-datenbank-trigger&quot;&gt;Raus aus der Java-Komfortzone: Datenbank-Trigger&lt;/h3&gt;
&lt;p&gt;Wenn ihr bereit seid, die Komfortzone der Java-Abstraktionsebenen zu verlassen und die Performanz eurer Auditing-Lösung verbessern wollt oder wenn ihr nach den umfangreichsten Anpassungsmöglichkeiten sucht, könnte triggerbasiertes Auditing die richtige Wahl für euch sein.&lt;/p&gt;

&lt;p&gt;Schauen wir uns an, wie wir unser Beispiel in ein triggerbasiertes Auditing-System umwandeln können.
Im ersten Schritt zeichnen wir jede Änderungsart in einer separaten Tabelle &lt;code class=&quot;highlighter-rouge&quot;&gt;dog_audit_log&lt;/code&gt; auf, die eine Spalte für &lt;code class=&quot;highlighter-rouge&quot;&gt;new_value&lt;/code&gt; und eine für &lt;code class=&quot;highlighter-rouge&quot;&gt;old_value&lt;/code&gt;, jeweils vom Typ &lt;code class=&quot;highlighter-rouge&quot;&gt;jsonb&lt;/code&gt;, beinhaltet. Wir verwenden &lt;code class=&quot;highlighter-rouge&quot;&gt;jsonb&lt;/code&gt; für eine einfache Handhabung und um Anpassungen flexibel zu gestalten. Änderungen in der Struktur der Entität &lt;code class=&quot;highlighter-rouge&quot;&gt;Dog&lt;/code&gt;, wie das Hinzufügen oder Entfernen von Spalten, beeinflussen unsere Audit-Log-Tabelle nicht.&lt;/p&gt;
&lt;figure&gt;
    &lt;img data-src=&quot;/img/posts/time-traveling-auditing/2024_02_21_angelina_blog2.jpg&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Zwei Tabellen, die linke bildet die Entität 'dog' ab, die rechte das Audit-Log&quot; /&gt;
    &lt;figcaption class=&quot;long-fig-caption&quot;&gt; 
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Damit wir das erreichen, setzen wir eine Funktion in der &lt;em&gt;PostgreSQL&lt;/em&gt;-Datenbank um, die die vorgenommenen Änderungen verarbeitet. Zudem müssen wir einen Trigger einrichten, der diese Funktion aktiviert, sobald eine &lt;code class=&quot;highlighter-rouge&quot;&gt;INSERT&lt;/code&gt;-, &lt;code class=&quot;highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt;- oder &lt;code class=&quot;highlighter-rouge&quot;&gt;DELETE&lt;/code&gt;-Operation durchgeführt wird.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog_audit_trigger&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;AFTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EACH&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dog_audit_trigger_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Es gibt auch automatisierte Lösungen, die Datenbank-Trigger nutzen, wie zum Beispiel &lt;em&gt;Debezium&lt;/em&gt;. Dieses Tool basiert auf &lt;em&gt;Kafka&lt;/em&gt; und extrahiert CDC-Events aus dem binären Log der Datenbank.
Um an die Audit-Daten zu kommen, müsst ihr die &lt;code class=&quot;highlighter-rouge&quot;&gt;dog_audit_log&lt;/code&gt;-Tabelle „manuell“ abfragen und sie mit der &lt;code class=&quot;highlighter-rouge&quot;&gt;Dog&lt;/code&gt;-Tabelle verknüpfen.&lt;/p&gt;

&lt;h4 id=&quot;vorteile-1&quot;&gt;Vorteile:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Entkoppelte Architektur:&lt;/strong&gt; Ein Hauptvorteil der triggerbasierten Überwachung ist ihre Unabhängigkeit von der Anwendung. Sie bietet eine nicht-intrusive Auditing-Lösung, die im Hintergrund läuft.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Umfassendes Auditing:&lt;/strong&gt; Diese Methode erfasst alle Änderungen – nicht nur die, die innerhalb der Anwendung erfolgen. Das macht sie ideal, um Daten aus verschiedenen Quellen zu prüfen und sicherzustellen, dass keine Änderungen übersehen werden.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;nachteile-1&quot;&gt;Nachteile:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Nicht mit Hibernate gekoppelt:&lt;/strong&gt; Da die triggerbasierte Überwachung nicht direkt in Hibernate integriert ist, könnte sie für Anwendungen, die stark auf Hibernate für die Datenverwaltung setzen, weniger geeignet sein.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Kaskadierende Trigger-Hölle:&lt;/strong&gt; Seid euch der möglichen Nebenwirkungen bewusst, die durch andere Datenbankoperationen ausgelöste kaskadierende Trigger haben könnten.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Herausforderungen beim Abrufen von Metadaten:&lt;/strong&gt; Das Herausziehen von Metadaten kann schwieriger sein und erfordert womöglich zusätzlichen Aufwand sowie Anpassungen, um die gewünschten Informationen zu erhalten.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;simples-auditing-jpa-data&quot;&gt;Simples Auditing: JPA Data&lt;/h3&gt;
&lt;p&gt;Um das Auditing einfach zu gestalten, bietet &lt;em&gt;JPA Data&lt;/em&gt; eine simple Lösung für grundlegende Audit-Anforderungen. Es ermöglicht uns, den Zeitpunkt der Erstellung oder Änderung einer Entität nachzuverfolgen, und stellt grundlegende Audit-Funktionen bereit. Und das ganz ohne die Komplexität, die mit &lt;em&gt;Hibernate Envers&lt;/em&gt; oder Datenbank-Trigger verbunden ist. Dafür können wir eine einfache Annotation nutzen, die von JPA Data zur Verfügung gestellt wird. Zusätzlich ist es notwendig, die Annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableJpaAuditing&lt;/code&gt; in der Konfigurationsklasse zu deklarieren. Werfen wir einen Blick auf unsere Beispiel-Entität &lt;code class=&quot;highlighter-rouge&quot;&gt;Dog&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EntityListeners&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuditingEntityListener&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Dog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@Id&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@CreatedDate&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdAt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@CreatedBy&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@LastModifiedDate&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedAt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;nd&quot;&gt;@LastModifiedBy&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;//Getters and setters&lt;/span&gt;

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

&lt;p&gt;Indem wir einfach vier weitere Attribute in unsere Entität einfügen, können wir verfolgen, wann und von wem sie erstellt oder geändert wurde. Im Unterschied zu Envers und triggerbasierten Lösungen werden diese Informationen jedoch nicht in einer separaten Tabelle gespeichert und bieten keinen Schnappschuss des vorherigen Zustands. Das bedeutet, JPA Data speichert nicht die Historie der Revisionen einer Entität, sondern lediglich, wann und von wem die aktuelle Version erstellt oder &lt;em&gt;zuletzt&lt;/em&gt; bearbeitet wurde.&lt;/p&gt;

&lt;p&gt;Obwohl diese Lösung nicht die umfassenden Anpassungsmöglichkeiten von Hibernate bietet, lässt sich der Funktionsumfang durch Anpassungen erweitern oder ändern, beispielsweise durch Modifizieren des verwendeten &lt;code class=&quot;highlighter-rouge&quot;&gt;EntityListener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Da keine Snapshots einer Entität gespeichert werden, ist es lediglich möglich, das aktuelle Entitätsobjekt abzufragen; einschließlich der Informationen, wann und von wem es zuletzt geändert oder erstellt wurde.&lt;/p&gt;

&lt;h4 id=&quot;vorteile-2&quot;&gt;Vorteile:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Einfachheit:&lt;/strong&gt; JPA Data besticht durch seine Einfachheit. Die Einrichtung und Handhabung sind kinderleicht, was es besonders für Projekte mit grundlegenden Audit-Anforderungen attraktiv macht.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Leistung:&lt;/strong&gt; Dank seiner einfachen Beschaffenheit hat JPA Data nur minimale Auswirkungen auf die Performance, sodass die Anwendungsleistung größtenteils unberührt bleibt.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;nachteile-2&quot;&gt;Nachteile:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Begrenzte Audit-Möglichkeiten:&lt;/strong&gt; Obwohl JPA Data für seine Einfachheit geschätzt wird, beschränkt es sich auf elementare Audit-Funktionen.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Keine Erfassung von Löschvorgängen:&lt;/strong&gt; Ein wesentlicher Nachteil von JPA Data ist, dass es keine Löschaktionen aufzeichnet. Das macht es für umfangreichere Audit-Szenarien, in denen die Verfolgung von Löschungen entscheidend ist, weniger geeignet.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;schlussfolgerung&quot;&gt;Schlussfolgerung&lt;/h3&gt;
&lt;p&gt;Zusammenfassend hängt die Entscheidung für die passende Auditing-Lösung, wie so oft, von den spezifischen Bedürfnissen und Prioritäten des Projekts ab.&lt;/p&gt;

&lt;p&gt;Hibernate Envers punktet mit nahtloser Integration, sodass man tief in der Java-Welt verwurzelt bleibt. Allerdings kann das zu einem erhöhten Leistungsaufwand führen.&lt;/p&gt;

&lt;p&gt;Triggerbasiertes Auditing ist optimal, um Änderungen aus verschiedenen Quellen zu erfassen, kann aber in der Einrichtung komplizierter sein und eine enge Anbindung an Hibernate vermissen lassen.&lt;/p&gt;

&lt;p&gt;JPA Data ist ideal für gewöhnliche Audit-Anforderungen, in denen Einfachheit und geringe Auswirkungen auf die Performance im Vordergrund stehen. Für fortgeschrittene Auditing-Szenarien könnte es jedoch nicht ausreichen.&lt;/p&gt;</content><author><name>angelinaMohr</name></author><summary type="html">Entdeckt effiziente Auditing-Methoden zur Nachverfolgung der Datenhistorie innerhalb von datenbankenbasierten Applikationen, einschließlich JPA Data und Hibernate Envers.</summary></entry><entry xml:lang="en"><title type="html">Web Accessibility: A Necessity for All</title><link href="https://blog.cronn.de/en/testing/accessibility/2024/02/29/web-accessibility-a-necessity-for-all.html" rel="alternate" type="text/html" title="Web Accessibility: A Necessity for All" /><published>2024-02-29T00:00:00-06:00</published><updated>2024-02-29T00:00:00-06:00</updated><id>https://blog.cronn.de/en/testing/accessibility/2024/02/29/web-accessibility-a-necessity-for-all</id><content type="html" xml:base="https://blog.cronn.de/en/testing/accessibility/2024/02/29/web-accessibility-a-necessity-for-all.html">&lt;h3 id=&quot;web-accessibility-a-necessity-for-everyone&quot;&gt;Web Accessibility: A Necessity for Everyone&lt;/h3&gt;
&lt;p&gt;Internet accessibility is a fundamental requirement which affects not only people with disabilities, but all of us. It provides seamless access to online content for seniors, children and people with visual impairments, but also for those with temporary limitations, such as a broken arm.&lt;/p&gt;

&lt;p&gt;Surprisingly, most disabilities are not congenital, but are caused by illness or accidents.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; This means that every one of us could be in need of accessible websites at some point. Therefore, it is imperative that websites facilitate the participation of people with disabilities in daily life.
From 28 June 2025, accessible products and services will become mandatory for most online shops and e-book providers. This is when the EU Accessibility Act (EAA) comes into force. Accessibility will then become compulsory for many website operators. This can be difficult to implement without automated accessibility testing.&lt;/p&gt;

&lt;h3 id=&quot;automated-accessibility-testing-with-axe&quot;&gt;Automated Accessibility Testing with Axe&lt;/h3&gt;
&lt;p&gt;To ensure that a website is accessible, regular checks for potential barriers are essential – ideally automated, as even minor changes can exclude some users by affecting the user experience.&lt;/p&gt;

&lt;p&gt;In this article, I’m going to share two ways to run frontend accessibility testing using &lt;a href=&quot;https://www.deque.com/axe/&quot;&gt;Axe&lt;/a&gt;, a tool from Deque. Not only is Axe free, but it’s also well-suited for automated testing and is one of the leading tools in the field. It is important to note that in creating this example, we expect errors to showcase the functionality of Axe.
It’s important to note that Axe alone may not fully detect all barriers, but it does provide an easy entry point to identify and fix errors. Axe has a JavaScript library that easily integrates with Java and Selenium.&lt;/p&gt;

&lt;h3 id=&quot;axes-integration-into-automated-tests&quot;&gt;Axe’s integration into automated tests&lt;/h3&gt;
&lt;p&gt;The integration of Axe into automated testing is a powerful accessibility testing tool that is easy to install and use. In the following section, I’ll show how Axe can be used in conjunction with &lt;a href=&quot;https://www.selenium.dev/&quot;&gt;Selenium&lt;/a&gt; and &lt;a href=&quot;https://cucumber.io/&quot;&gt;Cucumber&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the first example, I’m using Java, but you can also use Axe in other programming languages and testing frameworks, such as Playwright (I’ll explain this in the second example below). Of course, all the necessary dependencies must be present in the project, including &lt;a href=&quot;https://mvnrepository.com/artifact/com.deque.html.axe-core/selenium&quot;&gt;Axe-Selenium integration&lt;/a&gt; or the &lt;a href=&quot;https://www.npmjs.com/package/@axe-core/playwright&quot;&gt;Axe-Playwright package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To illustrate the structure of the project, here are examples of the directory structure for both Java/Selenium and TypeScript/Playwright:
Here for Java with Selenium:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;└───a11y-minimal-selenium
    │
    ├───axeReports
    │ AccessibilityViolations_XYZ.txt
    │
    └───src
        ├───main
        │   ├───java
        │   └───resources
        │
        └───test
            ├───java
            │ AccessibilityTestSteps.java
            │ TestRunner.java
            │
            └───resources
                    a11y.feature
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here for TypeScript with Playwright:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;└───a11y-minimal-playwright
    │
    ├───axeReports
    │   └───artifacts
    │           accessibility-scan-results-XYZ.html
    │
    ├───test
    │   ├───features
    │   │       a11y.feature
    │   │
    │   └───steps
    │           a11y.steps.ts
    │
    └───test-results
            cucumber-report.html

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

&lt;p&gt;In both cases, I use Cucumber to describe our requirement in a specific scenario. In our case, this scenario is that our website is to be barrier-free.&lt;/p&gt;

&lt;div class=&quot;language-gherkin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#  a11y.feature&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;Feature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; Accessibility Testing

  &lt;span class=&quot;kn&quot;&gt;Scenario&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; Verify accessibility of a web page
    &lt;span class=&quot;nf&quot;&gt;Given &lt;/span&gt;I open the web page &lt;span class=&quot;s&quot;&gt;&quot;www.cronn.de/&quot;&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;When &lt;/span&gt;I check the accessibility of the page
    &lt;span class=&quot;nf&quot;&gt;Then &lt;/span&gt;accessibility violations should be found
    &lt;span class=&quot;nf&quot;&gt;And &lt;/span&gt;a report is generated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we will take a closer look at the individual steps in both variants: Java/Selenium and TypeScript/Playwright.&lt;/p&gt;

&lt;h3 id=&quot;accessibility-testing-with-javaselenium&quot;&gt;Accessibility testing with Java/Selenium&lt;/h3&gt;
&lt;p&gt;After the webpage has been opened and the axeBuilder has been defined as &lt;code class=&quot;highlighter-rouge&quot;&gt;new AxeBuilder()&lt;/code&gt;, the accessibility analysis is performed in the &lt;code class=&quot;highlighter-rouge&quot;&gt;When I check the accessibility of the page&lt;/code&gt; step. This can look like this:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;AccessibilityTestSteps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;java&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@When&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I check the accessibility of the page&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;i_check_the_accessibility_of_the_page&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;axeResults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axeBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;analyze&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, it is also possible to check only for certain options or check only for specific rules or tags, which can be found on the &lt;a href=&quot;https://dequeuniversity.com/rules/axe/html/4.8&quot;&gt;Axe Rules page&lt;/a&gt;. You can also include and exclude certain elements and combine everything as you wish.
It might look like this (where the individual options, rules, etc. have been defined beforehand):&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axeResults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axeBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withOptions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withRules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;axeRules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withTags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;axeTags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;disableRules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;disabledRules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#excludedElement&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;analyze&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we check to see if we can find accessibility issues in our instance of &lt;code class=&quot;highlighter-rouge&quot;&gt;Results&lt;/code&gt;  called &lt;code class=&quot;highlighter-rouge&quot;&gt;axeResults&lt;/code&gt;. This is also very convenient with:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;AccessibilityTestSteps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;java&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Then&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;accessibility violations should be found&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;accessibility_violations_should_be_found&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assertFalse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Accessibility violations found&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axeResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getViolations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can then write the accessibility problems we find in our report. This can be designed completely according to your own requirements and the following is only a minimal example of a text file with an output during the test:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;AccessibilityTestSteps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;java&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@And&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a report is generated&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;a_report_is_generated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Rule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;violations&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axeResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getViolations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;violations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Violations: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;violations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AxeReportPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user.dir&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;separator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;axeReports&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;separator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeStamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleDateFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;yyyy_MM_dd_HH_mm_ss&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AxeViolationReportPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AxeReportPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;AccessibilityViolations_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeStamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;assertTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getReadableAxeResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResultType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Violations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;violations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;AxeReporter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;writeResultsToTextFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AxeViolationReportPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AxeReporter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAxeResultString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;No Accessibility violations found&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This accessibility test uses the webdriver setup of this test project along with Axe-webdriverjs and calls &lt;code class=&quot;highlighter-rouge&quot;&gt;AxeBuilder&lt;/code&gt; to then parse the page with Axe-core.
As soon as the test results are returned, they can be output directly in the console or via generated reports.
There is also a direct explanation of what the problem is and where it is located. In addition, reference is made directly to the appropriate rule for further explanations:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ACCESSIBILITY CHECK
VIOLATIONS check for: www.cronn.de/
Found 1 items

1: Heading levels should only increase by one
Description: Ensures the order of headings is semantically correct
Help URL: https://dequeuniversity.com/rules/axe/4.7/heading-order?application=axeAPI
Impact: moderate
Tags: cat.semantics, best-practice
        HTML element: &amp;lt;h4 class=&quot;title text-center&quot;&amp;gt;agile&amp;lt;/h4&amp;gt;
        Selector: [.col-md-3.col-lg-2.col-6:nth-child(2) &amp;gt; .title]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;accessibility-testing-with-typescriptplaywright-and-cucumber&quot;&gt;Accessibility testing with TypeScript/Playwright and Cucumber&lt;/h3&gt;
&lt;p&gt;Firstly, the necessary packages need to be installed. Secondly, the accessibility tests can then be implemented. Here is a simple example using TypeScript and Playwright.&lt;/p&gt;

&lt;p&gt;After the website has opened and the axeBuilder has been defined in our class as the &lt;code class=&quot;highlighter-rouge&quot;&gt;new AxeBuilder()&lt;/code&gt;, we want to perform the accessibility analysis in the &lt;code class=&quot;highlighter-rouge&quot;&gt;When I check the accessibility of the page&lt;/code&gt; step. This is done easily with:&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// a11y.steps.ts&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;When&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;I check the accessibility of the page&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;accessibilityScanResults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AxeBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;analyze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Of course, it is also possible to include and exclude certain elements in this way, as already shown above.&lt;/p&gt;

&lt;p&gt;In the last step, it is checked if we find any problems, in other words: if our checklist is complete.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// a11y.steps.ts&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;accessibility violations should be found&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;accessibilityScanResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;violations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeGreaterThan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here, too, we generate reports from the accessibility problems found, this time as an HTML report, and check that it is complete.&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// a11y.steps.ts&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a report is generated&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;-T:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`accessibility-scan-results-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.html`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axeReportPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;axeReports&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;createHtmlReport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;accessibilityScanResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;outputDirPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;axeReportPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;reportFileName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;accessibilityScanResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;violations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;`A11y report in &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here, you can view the clear, HTML-formatted report:&lt;/p&gt;
&lt;figure&gt;
    &lt;img data-src=&quot;/img/posts/barrierefreiheit-im-web/a11y-report-html.png&quot; class=&quot;lazyload img-fluid img-feature&quot; alt=&quot;Accessibility test results as HTML-formatted report&quot; /&gt;
    &lt;figcaption class=&quot;long-fig-caption&quot;&gt; 
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Ensuring the accessibility of websites for people of different abilities is of fundamental importance. Using Axe simplifies the process of performing automated accessibility testing and provides an excellent starting point for identifying and resolving issues.&lt;/p&gt;

&lt;p&gt;Axe’s integration into automated testing enables effective accessibility monitoring, regardless of whether Java/Selenium or TypeScript/Playwright is being used. Clear reporting and customization options make it easier to identify accessibility violations, which is particularly important in the context of societal obligations and ever-increasing legal requirements.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Further information on disabilities in Germany can be found in the &lt;a href=&quot;https://www.destatis.de/EN/Press/2018/06/PE18_228_227.html&quot;&gt;press release from the federal statistical office&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>ann-kathrinFroin</name></author><summary type="html">The automation of accessibility tests is of great importance and easy to implement with Axe.</summary></entry></feed>