<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PDF Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/pdf/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:50:21 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>PDF Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Generate a PDF in a Spring Boot with Apache PDFBox and Kotlin</title>
		<link>https://blog.codersee.com/sring-boot-kotlin-apache-pdfbox/</link>
					<comments>https://blog.codersee.com/sring-boot-kotlin-apache-pdfbox/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 28 Dec 2021 07:30:57 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[PDF]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=1838</guid>

					<description><![CDATA[<p>In this guide, I will teach you how to generate and secure a PDF in a Spring Boot REST API with Apache PDFBox and Kotlin.</p>
<p>The post <a href="https://blog.codersee.com/sring-boot-kotlin-apache-pdfbox/">Generate a PDF in a Spring Boot with Apache PDFBox and Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2 class="article-heading-introduction">1. Introduction</h2>
<p>In this guide, I will walk you step by step through the process of generating and securing a <strong>PDF</strong> in a <strong>Spring Boot REST API</strong> with <a href="https://pdfbox.apache.org/">Apache PDFBox</a> and <strong>Kotlin</strong>. After this tutorial you will be able to create PDF reports containing text and tables and expose them through the REST API in Spring Boot. Additionally, I will show you how to secure them with owner and user passwords and apply policies to them.</p>
<p>To better visualize the final effect, these are the two pages generated in this tutorial:</p>
<p><img fetchpriority="high" decoding="async" class="alignnone wp-image-1841 size-full" src="http://blog.codersee.com/wp-content/uploads/2021/12/PDF_PREVIEW_RESULT.png" alt="Image shows an example PDF generated in a Spring Boot project written in Kotlin with a help of Apache PDFBox and Boxable libraries." width="1200" height="630" srcset="https://blog.codersee.com/wp-content/uploads/2021/12/PDF_PREVIEW_RESULT.png 1200w, https://blog.codersee.com/wp-content/uploads/2021/12/PDF_PREVIEW_RESULT-300x158.png 300w, https://blog.codersee.com/wp-content/uploads/2021/12/PDF_PREVIEW_RESULT-1024x538.png 1024w, https://blog.codersee.com/wp-content/uploads/2021/12/PDF_PREVIEW_RESULT-768x403.png 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></p>
<h2 class="article-heading-main">2. Imports</h2>
<p>Let&#8217;s start everything by adding a few dependencies to our Gradle project:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="groovy">implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")

implementation("org.apache.pdfbox:pdfbox:2.0.24")
implementation("com.github.dhorions:boxable:1.6")
</pre>
<p>Personally, each time I start a new project, I use the <a href="http://start.spring.io/" target="_blank" rel="noopener noreferrer">Spring Initializr</a>, which allows selecting necessary libraries pretty easily.</p>
<p>Similarly, I did this time, but you might have already noticed two more libraries we will need to generate the PDF:</p>
<ul>
<li><a href="https://pdfbox.apache.org/" target="_blank" rel="noopener">Apache PDFBox</a></li>
<li><a href="https://github.com/dhorions/boxable" target="_blank" rel="noopener">Boxable</a></li>
</ul>
<p>Basically, the first one is an open-source Java tool for working with PDF documents. I will prove to you in the next chapters, how easy and neat the whole process of creating a PDF in Spring Boot becomes with this library.</p>
<p><a href="http://dhorions.github.io/boxable/">Boxable</a>, on the other hand, is a great extension for the first one, which will help us generate tables.</p>
<h2 class="article-heading-main">3. Create Test Data Provider</h2>
<p>As the next step, let&#8217;s implement a logic responsible for example data generation:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">object PeopleDataProvider {

  fun generateTestData(): List&lt;Person&gt; =
    listOf(
      Person("John", "Doe", "mail1@codersee.com", 22),
      Person("Emma", "Smith", "mail2@codersee.com", 20),
      Person("Wayne", "Johnson", "mail3@codersee.com", 30),
      Person("Robert", "Robertson", "mail4@codersee.com", 41),
      Person("Sophia", "Miller", "mail5@codersee.com", 27),
      Person("Adam", "Williams", "mail6@codersee.com", 52)
    )

  data class Person(
    val firstName: String,
    val lastName: String,
    val email: String,
    val age: Int
  )
}
</pre>
<p>The <em><strong>PeopleDataProvider</strong> </em>is a simple Kotlin object with one function returning a list of random people&#8217;s data.</p>
<p>In a real-life scenario, this structure can be easily replaced with some other logic, like fetching from the database.</p>
<h2 class="article-heading-main">4. Create Controller</h2>
<p>After that, let&#8217;s prepare the code responsible for exposing a<strong> REST endpoint</strong>, which will be used to fetch the PDF in our Spring Boot Kotlin app. (And please don&#8217;t worry about missing <em>PdfService</em>, we will take care of it in the next chapter):</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@RestController
class PdfController(
  private val pdfService: PdfService
) {

  @PostMapping("/api/report")
  fun getAllUsersCsvExport(response: HttpServletResponse): ResponseEntity&lt;ByteArray&gt; {
    val report = pdfService.generate()

    return ResponseEntity.ok()
      .contentType(MediaType.APPLICATION_PDF)
      .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"report.pdf\"")
      .body(report)
  }
}
</pre>
<p>To put it simply, the above method handles all <em>POST /api/report</em> requests by invoking the <em>generate()</em> method. It produces a <em>ByteArray</em> object, which ends ups as a response body. Whatsoever, we explicitly specify two additionals headers:</p>
<ul>
<li><strong>Content-Type</strong>&#8211; indicating the original media type of the resource (PDF in our case)</li>
<li><strong>Content-Disposition</strong>&#8211; instructing the client, whether the content is expected to be inlined in the browser, or downloaded as an attachment (and I believe there is no doubt about which one we&#8217;ve picked here)</li>
</ul>
<h2 class="article-heading-main">5. Create PdfService</h2>
<p>Nextly, let&#8217;s implement the missing <em>PdfService</em>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Service
class PdfService(
  private val firstPageGenerator: FirstPageGenerator
) {

  fun generate(): ByteArray {
    val document = PDDocument()

    val firstPage = firstPageGenerator.generate(document)
    document.addPage(firstPage)

    val byteArray = generateByteArray(document)
    document.close()
    return byteArray
  }

  private fun generateByteArray(document: PDDocument): ByteArray {
    val outputStream = ByteArrayOutputStream()
    document.save(outputStream)
    return outputStream.toByteArray()
  }

}
</pre>
<p>Just like in the previous example, the compiler complains about missing class and we will get rid of this error in the next chapter. But for now, let&#8217;s figure out what&#8217;s going on here.</p>
<p>In the first line, we create a <em>PDDocument</em> instance. At this point, it&#8217;s just an <strong>empty PDF document</strong>. Nevertheless, it&#8217;s not valid as long as we don&#8217;t add at least one page to it. As you might have already noticed, to do that we have to use the <em>addPage()</em> method, which as an argument takes the <em>PDPage</em> instance returned by the <em>FirstPageGenerator</em>.</p>
<p>Finally, we pass the document to the <em>generateByteArray()</em> function, which saves it to the output stream and converts it to the byte array.</p>
<h2 class="article-heading-main">6. Implement FirstPageGenerator</h2>
<p>With all of that being done, let&#8217;s prepare a class responsible for the first page of the PDF:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Component
class FirstPageGenerator {

  companion object {
    private const val FONT_SIZE = 50
    private const val TITLE = "Example test report"

    private val FONT = PDType1Font.TIMES_BOLD
  }

  fun generate(document: PDDocument): PDPage {
    val page = PDPage()

    val titleWidth = calculateTitleWidth()
    val horizontalOffset = calculateHorizontalOffset(page, titleWidth)
    val verticalOffset = calculateVerticalOffset(page)

    val contentStream = PDPageContentStream(document, page)
    editContent(contentStream, horizontalOffset, verticalOffset)

    return page
  }

  private fun editContent(
    contentStream: PDPageContentStream,
    horizontalOffset: Float,
    verticalOffset: Float
  ) {
    contentStream.beginText()
    contentStream.setFont(FONT, FONT_SIZE.toFloat())
    contentStream.newLineAtOffset(horizontalOffset, verticalOffset)
    contentStream.showText(TITLE)
    contentStream.endText()
    contentStream.close()
  }

  private fun calculateVerticalOffset(page: PDPage): Float =
    (page.mediaBox.height) / 2

  private fun calculateHorizontalOffset(page: PDPage, titleWidth: Float): Float =
    (page.mediaBox.width - titleWidth) / 2

  private fun calculateTitleWidth(): Float =
    FONT.getStringWidth(TITLE) / 1000 * FONT_SIZE
}
</pre>
<p>Basically, the whole purpose of this code is to generate a new <em>PDPage</em> instance, which will become the first page of the final document. However, the most important class here is the <em>PDPageContentStream</em>, which provides the ability to write a page content stream.</p>
<p>Moreover, we use the constructor overriding all existing content streams. In simple words, if the page instance passed to it contains any data, it will be removed. If we would like to put multiple content streams to it, then the library provides additional constructors allowing us to explicitly specify the behavior, like appending or prepending.</p>
<p>As you might have already noticed, the whole magic happens inside the <em>editContent()</em> function. The <em>PDPageContentStream</em> provides plenty of methods to work with, but it all depends on the particular things we would like to display. In our case, we write the predefined title with Times-Bold font and size set to 50 in the very center of the page.</p>
<p>Additionally, please remember that after we finish working with the content stream, we must call the<em> close()</em> method.</p>
<h2 class="article-heading-main">7. Testing PDF Spring Boot Generator</h2>
<p>At this point, we should be able to finally generate the first version of the PDF document in our Spring Boot app. To do so, let&#8217;s run the app and invoke the <em>cURL</em> command:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">curl -X POST http://localhost:8080/api/report --output file.pdf
</pre>
<p>As a result, the document should be generated and persisted within the directory. Please keep in mind, that it can be done with Postman, or another similar tool, as well.</p>
<h2 class="article-heading-main">8. Create Table Page</h2>
<p>With that being done, let&#8217;s add a second page to our document.</p>
<h3 class="article-heading-sub">8.1. Implement TablePageGenerator</h3>
<p>Firstly, let&#8217;s create a <em>TablePageGenerator</em> class:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Component
class TablePageGenerator {

  companion object {
    private const val MARGIN = 30f
    private const val TOP_MARGIN = 30f
    private const val BOTTOM_MARGIN = 30f

    // specified in px
    private const val ROW_HEIGHT = 20f

    // % of table width
    private const val CELL_WIDTH = 20f

    private val TABLE_WIDTH = PDRectangle.A4.width - 60
    private val Y_TABLE_START = PDRectangle.A4.height - 100
  }

  fun generate(document: PDDocument, people: List&lt;PeopleDataProvider.Person&gt;): PDPage {
    val page = PDPage()

    val contentStream = PDPageContentStream(document, page)
    contentStream.beginText()

    val table = createTable(document, page)
    addHeaderRow(table)
    addDataRows(people, table)
    table.draw()

    contentStream.endText()
    contentStream.close()

    return page
  }

  private fun createTable(document: PDDocument, page: PDPage): BaseTable =
    BaseTable(
      Y_TABLE_START, Y_TABLE_START, TOP_MARGIN, BOTTOM_MARGIN, TABLE_WIDTH,
      MARGIN, document, page, true, true
    )

  private fun addHeaderRow(table: BaseTable) {
    val row: Row&lt;PDPage&gt; = table.createRow(ROW_HEIGHT)

    createBoldHeaderCell(row, "Number")
    createBoldHeaderCell(row, "First Name")
    createBoldHeaderCell(row, "Last Name")
    createBoldHeaderCell(row, "Email")
    createBoldHeaderCell(row, "Age")
    table.addHeaderRow(row)
  }

  private fun createBoldHeaderCell(row: Row&lt;PDPage&gt;, value: String) {
    row
      .createCell(CELL_WIDTH, value)
      .font = PDType1Font.TIMES_BOLD
  }

  private fun addDataRows(
    people: List&lt;PeopleDataProvider.Person&gt;,
    table: BaseTable
  ) {
    people.forEachIndexed { index, person -&gt;
    val row = table.createRow(ROW_HEIGHT)
    createCell(row, (index + 1).toString())
    createCell(row, person.firstName)
    createCell(row, person.lastName)
    createCell(row, person.email)
    createCell(row, person.age.toString())
    }
  }

  private fun createCell(row: Row&lt;PDPage&gt;, value: String) {
    row
      .createCell(value)
      .font = PDType1Font.TIMES_ROMAN
  }
}
</pre>
<p>Just like in the previous example, everything starts and ends with the <em>PDPage</em> and <em>PDPageContentStream</em> instances. However, this time, we make use of the <strong>Boxable</strong> library. I&#8217;ve mentioned in the beginning that it really simplifies the whole process of table generation for PDF in Spring Boot and I believe the above code proves it best.</p>
<p>As we can see, to generate a new table we need to create a <em>BaseTable</em> instance and pass the necessary parameters to its constructor. Additionally, all of the arguments taken from the companion object are pretty descriptive. Nevertheless, as a word of explanation- the two last boolean flags set the <em>drawLines</em> and the <em>drawContent</em> to true. As always, I highly encourage you to check it out with different, custom settings.</p>
<p>After that is done, we add the header row and data rows for each person from the provided list. To put it simply, we use the table instance, to add a new Row object with the createRow method, which is then used to add cells with the createCell function.</p>
<h3 class="article-heading-sub">8.2. Edit PdfService</h3>
<p>Nextly, we need to get back to the <em>PdfService</em> class and make use of our new class:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Component
class PdfService(
  private val firstPageGenerator: FirstPageGenerator,
  private val tablePageGenerator: TablePageGenerator
) {

  fun generate(): ByteArray {
    val document = PDDocument()

    val people = PeopleDataProvider.generateTestData()
    val firstPage = firstPageGenerator.generate(document)
    val tablePage = tablePageGenerator.generate(document, people)

    document.addPage(firstPage)
    document.addPage(tablePage)
//... the rest of the code
</pre>
<p>Similarly to the first page, we&#8217;ve just added the <em>TablePageGenerator</em> to the constructor (line 4), and added to the document (line 15) the <em>PDPage</em> instance (line 12) generated with the data from our test provider (line 10).</p>
<p>After it&#8217;s done, let&#8217;s rerun the command from chapter 5. As a result, we should see that the generated PDF contains two, beautiful pages with a title and a table.</p>
<h2 class="article-heading-main">9. Secure The Document</h2>
<p>Finally, let&#8217;s learn how to secure a PDF generated in the Spring Boot app and set additional policies.</p>
<h3 class="article-heading-sub">9.1. Create ProtectionPolicyService</h3>
<p>Let&#8217;s add the <em>ProtectionPolicyService</em>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Component
class ProtectionPolicyService {

  companion object {
    // either 40, 128 or 256
    private const val ENCRYPTION_KEY_LENGTH = 256
    private const val OWNER_PASSWORD = "owner"
    private const val USER_PASSWORD = "user"
  }

  fun generateStandardProtectionPolicy(): StandardProtectionPolicy {
    val accessPermission = AccessPermission()
    accessPermission.setCanPrint(false)
    accessPermission.setCanExtractContent(false)
    return getStandardProtectionPolicy(accessPermission)
  }

  private fun getStandardProtectionPolicy(accessPermission: AccessPermission): StandardProtectionPolicy {
    val protectionPolicy = StandardProtectionPolicy(OWNER_PASSWORD, USER_PASSWORD, accessPermission)
    protectionPolicy.encryptionKeyLength = ENCRYPTION_KEY_LENGTH
    return protectionPolicy
  }
}
</pre>
<p>The <em>StandardProtectionPolicy</em> is a simpler implementation of the <em>ProtectionPolicy</em> class. With it we can configure password-based protection and specific permissions for users. It&#8217;s worth mentioning, that it allows us to only distinguish between the <strong>owner</strong> (full permissions) and a<strong> &#8220;standard&#8221; user</strong> (with permissions defined by the <em>AccessPermission</em> instance). If we would like to gain more control over the recipients&#8217; privileges, then the <em>PublicKeyProtectionPolicy</em> would be the better choice. In the contrary, it allows us to handle multiple recipients with different permissions.</p>
<p>But for now, let&#8217;s focus on the code we&#8217;ve just written. I&#8217;ve mentioned above the <em>AccessPermission</em> class. As we can see, it allows us to define what exactly users can or can not do. By default, all permissions are granted. But given the above snippet we can clearly see that with this configuration users won&#8217;t be able to print or extract the content of the document. Nevertheless, it&#8217;s just a small part of all permissions we can grant and I highly encourage you to check out other possibilities.</p>
<p>Finally, we set the owner&#8217;s and users&#8217; passwords followed by the encryption key length.</p>
<h3 class="article-heading-sub">9.2. Modify PdfService</h3>
<p>As the last step, we need to make use of the created code:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Service
class PdfService(
  private val firstPageGenerator: FirstPageGenerator,
  private val tablePageGenerator: TablePageGenerator,
  private val protectionPolicyService: ProtectionPolicyService
) {

  fun generate(): ByteArray {
    val document = PDDocument()

    val people = PeopleDataProvider.generateTestData()
    val firstPage = firstPageGenerator.generate(document)
    val tablePage = tablePageGenerator.generate(document, people)
    val protectionPolicy = protectionPolicyService.generateStandardProtectionPolicy()

    document.addPage(firstPage)
    document.addPage(tablePage)
    document.protect(protectionPolicy)
//... the rest of the code</pre>
<p>Similarly to the 8.2, we&#8217;ve added the constructor parameter (line 5) and protected the document (line 18) with the generated policy (line 14).</p>
<p>And again, let&#8217;s rerun the application and invoke the test command to validate that everything behaves as expected. We should be able to print the document specifying the owner password. In contrary, it should be forbidden when the standard user password has been used.</p>
<h2 class="article-heading-main">10. Generate PDF With Spring Boot Summary</h2>
<p>That&#8217;s all for today&#8217;s tutorial. I really hope that you enjoyed it and that it will help you generating your own PDFs using Apache PDFBox. The knowledge you&#8217;ve gathered today should be enough to start exploring this library and apply functionalities in your own use-cases.</p>
<p>Finally, if you would like to get the source code, please visit this <a href="https://github.com/codersee-blog/kotlin-spring-boot-create-apache-pdfbox" target="_blank" rel="noopener">GitHub repository</a>.</p>
<p>As always, if you would like to ask about anything or need some more explanation, please do it in the comment section below, or by using the <a href="https://codersee.com/contact/" target="_blank" rel="noopener noreferrer">contact</a> form.</p>
<p>&nbsp;</p>
<h4 class="article-heading-sub">If you find this material useful, you might be interested in my previous articles:</h4>
<ul>
<li><a href="https://blog.codersee.com/mono-just-defer-fromsupplier-create-part-2/">Mono.just() vs defer() vs fromSupplier() vs create() &#8211; Part 2</a></li>
<li><a href="https://blog.codersee.com/what-is-kotlin-and-why-should-you-learn-it/">What is Kotlin and Why Should You Learn It?</a></li>
<li><a href="https://blog.codersee.com/mono-just-defer-fromsupplier-create-part-1/">Mono.just() vs defer() vs fromSupplier() vs create() &#8211; Part 1</a></li>
</ul>
<p>The post <a href="https://blog.codersee.com/sring-boot-kotlin-apache-pdfbox/">Generate a PDF in a Spring Boot with Apache PDFBox and Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/sring-boot-kotlin-apache-pdfbox/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-05-17 12:08:07 by W3 Total Cache
-->