Getting started · 0.2.0

From mapped bookings to a complete EXTF file.

Start with the fixed-schema plain exporter. Add typed metadata and metadata-aware validation, then choose retained or forward-only output based on your lifecycle.

Step 0

Requirements and module choice

  • Java 17 or newer.
  • datev-exporter-plain for the fixed v13/v12 schemas and output.
  • datev-exporter-field-validator when deterministic semantic checks should run before each row is accepted.

The root datev-exporter artifact is a Bill of Materials (BOM), not a runtime JAR. Import it to keep module versions aligned, then depend on the modules you use.

Bring mapped accounting data

The examples use placeholder adviser/client numbers and account assignments. Replace every business value with data approved for your target ledger; the library does not map SKR03/SKR04 or determine tax treatment.

Step 1A

Run with Gradle

Create a Java application, place the Java example below in src/main/java/Example.java, then use this build.gradle:

plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation platform('io.github.mrtyldr:datev-exporter:0.2.0')
    implementation 'io.github.mrtyldr:datev-exporter-plain'
    implementation 'io.github.mrtyldr:datev-exporter-field-validator'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    mainClass = 'Example'
}

Run it with your Gradle wrapper:

./gradlew run

The repository also contains a deterministic, CI-executed application:

git clone https://github.com/mrtyldr/datev-exporter.git
cd datev-exporter
./gradlew :examples:quickstart-gradle:run

Step 1B

Build with Maven

The same Example.java works with this minimal pom.xml. The BOM owns the module versions:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>datev-quickstart</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.github.mrtyldr</groupId>
        <artifactId>datev-exporter</artifactId>
        <version>0.2.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>io.github.mrtyldr</groupId>
      <artifactId>datev-exporter-plain</artifactId>
    </dependency>
    <dependency>
      <groupId>io.github.mrtyldr</groupId>
      <artifactId>datev-exporter-field-validator</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.15.0</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.6.3</version>
        <configuration>
          <mainClass>Example</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Compile and run with:

mvn verify exec:java

Step 2

Create a complete v13 file

This application writes all three required record layers: EXTF management record, the exact 125-column v13 heading, and one booking row. DatevFile retains accepted rows so they can be inspected or written again.

import io.github.mrtyldr.datev.core.DatevColumn;
import io.github.mrtyldr.datev.core.DatevField;
import io.github.mrtyldr.datev.core.DatevMetadata;
import io.github.mrtyldr.datev.core.DatevValidationContext;
import io.github.mrtyldr.datev.plain.DatevFile;
import io.github.mrtyldr.datev.validation.DatevValidator;

import java.io.OutputStream;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.LocalDateTime;

public final class Example {
    public static void main(String[] args) throws Exception {
        LocalDate fiscalStart = LocalDate.of(2026, 1, 1);
        LocalDate periodStart = LocalDate.of(2026, 8, 1);
        LocalDate periodEnd = LocalDate.of(2026, 8, 31);

        DatevMetadata metadata = DatevMetadata.bookingBatchV13()
                .createdAt(LocalDateTime.now())
                .origin("RE")
                .exportedBy("my_application")
                .advisorNumber(1001)
                .clientNumber(1)
                .fiscalYearStart(fiscalStart)
                .accountLength(4)
                .period(periodStart, periodEnd)
                .description("August 2026")
                .applicationInformation("my-application")
                .build();

        DatevValidationContext context = DatevValidationContext.builder()
                .accountLength(metadata.accountLength())
                .fiscalYearStart(metadata.fiscalYearStart())
                .period(metadata.periodStart(), metadata.periodEnd())
                .build();
        DatevValidator validator = DatevValidator.builder()
                .context(context)
                .build();

        DatevFile file = DatevFile.builder()
                .metadata(metadata)
                .validator(validator)
                .build();
        file.append(
                DatevColumn.amount(DatevField.AMOUNT,
                        new BigDecimal("1250.00")),
                DatevColumn.of(DatevField.DEBIT_CREDIT_FLAG, "S"),
                DatevColumn.of(DatevField.CURRENCY, "EUR"),
                DatevColumn.account(DatevField.ACCOUNT, 1000),
                DatevColumn.account(DatevField.CONTRA_ACCOUNT, 8400),
                DatevColumn.documentDate(LocalDate.of(2026, 8, 10)),
                DatevColumn.of(DatevField.DOCUMENT_FIELD_1, "RE-42"),
                DatevColumn.of(DatevField.POSTING_TEXT, "Invoice 42")
        );

        try (OutputStream output = Files.newOutputStream(
                Path.of("EXTF_Buchungsstapel.csv"))) {
            file.writeTo(output);
        }
    }
}
Why DatevField?

Its readable English constants carry the exact German output headings. A misspelled field becomes a compile-time error while the emitted heading remains canonical.

Large exports

Stream through a buffered destination

DatevStreamWriter writes the management record and heading when it is built, then prepares and hands off one booking row per append without retaining successful rows. Wrap an otherwise unbuffered file or network destination in BufferedOutputStream to reduce small operating-system writes.

import io.github.mrtyldr.datev.plain.DatevStreamWriter;

import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

try (OutputStream file = Files.newOutputStream(
             Path.of("EXTF_Buchungsstapel.csv"));
     OutputStream output = new BufferedOutputStream(file);
     DatevStreamWriter writer = DatevStreamWriter.builder()
             .metadata(metadata)
             .validator(validator)
             .build(output)) {
    for (Map<String, ?> row : bookingRows) {
        writer.append(row);
    }
}
  • The default JDK buffer size is a sound starting point; tune only from measurements.
  • Do not add another buffer around ByteArrayOutputStream, StringWriter or an already buffered destination.
  • The writer flushes but does not close the caller-owned destination. Declaring it last makes try-with-resources close the writer first, then the buffer and file.
  • A validation failure leaves the current output unchanged. An I/O failure can occur after a destination accepted part of a row; physical rollback is impossible, and the writer becomes terminal.

Library-managed working memory follows the row being assembled rather than the total row count. A validator or destination such as ByteArrayOutputStream may still retain data, so choose a genuinely streaming destination when heap use matters.

Legacy contract

Select v12 deliberately

Use matching v12 metadata and schema. The builder rejects a management-record version that does not match its 124-column heading.

import io.github.mrtyldr.datev.core.DatevSchema;

DatevMetadata legacyMetadata = DatevMetadata.bookingBatchV12()
        .createdAt(LocalDateTime.now())
        .origin("RE")
        .exportedBy("my_application")
        .advisorNumber(1001)
        .clientNumber(1)
        .fiscalYearStart(fiscalStart)
        .accountLength(4)
        .period(periodStart, periodEnd)
        .build();

DatevFile legacy = DatevFile.builder(DatevSchema.LEGACY_V12)
        .metadata(legacyMetadata)
        .build();
Lower evidence level for v12

v12 support is structurally derived from the first 124 v13 fields and covered by library tests. Release 0.2.0 has no independently pinned official v12 fixture and no recorded real-target acceptance result. See the version matrix.

Final step

Verify beyond the library

  1. Test your mapping

    Assert the approved accounts, tax choices, dates and source-to-field mapping in your application.

  2. Inspect the file boundary

    Keep generated bytes as Windows-1252 with CRLF records; do not let a later UTF-8 text step rewrite them.

  3. Use DATEV tooling and the real target

    Run the applicable checker workflow and a controlled import in the licensed, configured target environment. Record the product/version and result for your own compatibility evidence.

Review claims and limitations Open API 0.2.0