https://github.com/g0ddest/fixedlength
Fast simple zero-dependency Java library to parse fixed length files
https://github.com/g0ddest/fixedlength
fixed-length fixed-length-fields fixed-length-format hacktoberfest hacktoberfest-accepted java no-dependencies
Last synced: 4 months ago
JSON representation
Fast simple zero-dependency Java library to parse fixed length files
- Host: GitHub
- URL: https://github.com/g0ddest/fixedlength
- Owner: g0ddest
- License: apache-2.0
- Created: 2020-12-28T11:32:07.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2026-02-09T19:48:33.000Z (4 months ago)
- Last Synced: 2026-02-09T22:35:57.669Z (4 months ago)
- Topics: fixed-length, fixed-length-fields, fixed-length-format, hacktoberfest, hacktoberfest-accepted, java, no-dependencies
- Language: Java
- Homepage:
- Size: 154 KB
- Stars: 22
- Watchers: 5
- Forks: 8
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Fixed Length handler for Java
[](https://search.maven.org/artifact/name.velikodniy.vitaliy/fixedlength)
[](https://sonarcloud.io/summary/new_code?id=g0ddest_fixedlength)
[](https://sonarcloud.io/summary/new_code?id=g0ddest_fixedlength)
[](https://javadoc.io/doc/name.velikodniy.vitaliy/fixedlength)
This is fast simple zero-dependency library for Java 8+ that aims to parse fixed length (files with entities placed on fixed place in every line) files.
Library was inspired by [Fixed Length File Handler](https://github.com/GuiaBolso/fixed-length-file-handler) and [fixedformat4j](https://github.com/jeyben/fixedformat4j).
One of its advantages is support mixed line types.
It works with `InputStream` so it is more memory efficient than store all file in memory. This is big
advantage when working with big files.
## Download
This library is published to Maven Central and to Github packages, so you'll need to configure that in your repositories:
Just ensure that you have
```groovy
repositories {
mavenCentral()
}
```
or optionally if you want you can get the package from the Github packages
Gradle:
```groovy
repositories {
mavenCentral()
maven {
url "https://maven.pkg.github.com/g0ddest/fixedlength"
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("TOKEN")
}
}
}
```
(you need to add property with your username and github token, or put them into system envs).
And then configure dependency:
Maven:
```xml
name.velikodniy.vitaliy
fixedlength
0.14
pom
```
Gradle:
```groovy
implementation 'name.velikodniy.vitaliy:fixedlength:0.14'
```
Ivy:
```xml
```
## Usage
### Basic usage
For example, you can transform this lines to 2 different kind of objects:
```
EmplJoe1 Smith Developer 07500010012009
CatSnowball 20200103
EmplJoe3 Smith Developer
```
It's usual when processing data in some legacy systems.
You just need to write class with field structure and annotate each field that you want to connect with your file.
To parse this simple file
```
Joe1 Smith
Joe3 Smith
```
you need just write down this class (annotated fields also could be pulled from annotated classes):
```java
public class Employee {
@FixedField(offset = 1, length = 10, align = Align.LEFT)
public String firstName;
@FixedField(offset = 10, length = 10, align = Align.LEFT)
public String lastName;
}
```
and run parser:
```java
List parse = new FixedLength()
.registerLineType(Employee.class)
.parse(fileStream);
```
### Mixed line types
If there are few line types in your file and they starts with different string you can register different line types.
To do this you should add annotation to your class:
```java
@FixedLine(startsWith = "Empl")
```
So you can parse this file:
```
EmplJoe1 Smith
CatSnowball
EmplJoe3 Smith
```
with these files:
```java
@FixedLine(startsWith = "Empl")
public class EmployeeMixed {
@FixedField(offset = 5, length = 10, align = Align.LEFT)
public String firstName;
@FixedField(offset = 15, length = 10, align = Align.LEFT)
public String lastName;
}
```
(fields could be final as well).
```java
@FixedLine(startsWith = "Cat")
public class CatMixed {
@FixedField(offset = 4, length = 10, align = Align.LEFT)
public String name;
@FixedField(offset = 14, length = 8, format = "yyyyMMdd")
public LocalDate birthDate;
}
```
and run parser like that:
```java
List parse = new FixedLength()
.registerLineType(EmployeeMixed.class)
.registerLineType(CatMixed.class)
.parse(fileStream);
```
### Custom formatters
If you need to use a custom class or type in parser you can add your own formatter like this:
```java
public class StringFormatter extends Formatter {
@Override
public String asObject(String string, FixedField field) {
return string;
}
}
```
and register it with `registerFormatter` method on `FixedLength` instance.
### Annotation parameters
There are all fields in `FixedField` annotation:
* `offset` — position on which this fields starts. Line starts with offset 1.
* `length` — length of the field
* `align` — on which side the content is justified. It works with padding.
* `padding` — based on align trimming filler symbols. For example `" 1"` becomes `"1"`.
* `format` — parameters that goes to formatter. For example, it can be date format.
* `divide` — for number fields you can automatically divide the value on 10^n where n is value of this parameter.
* `ignore` — the parser will ignore the field content if it matches the given regular expression. For example, `"0{8}"` will ignore `"00000000"`
* `allowEmptyStrings` — the parser will keep empty strings instead of replacing them with `null`
* `fallbackStringForNullValue` — when formatting an object back to a fixed length string, the formatter will replace a `null` value for this field with the given fallback string. If the fallback string is shorter than the field length, it will be padded according to the specified alignment and padding character.
### Generics support
You can also use generics to cast parsed object to desired class.
It is more convenient if you have file with one entity type.
```java
List parse = new FixedLength()
.registerLineType(Employee.class);
```
### Ignoring errors
If there is errors on your line format there are two modes that you could skip these errors if you want to:
* `skipErroneousLines` — line with error will not be added to result.
* `skipErroneousFields` — fields with errors will be `null`.
In both cases warnings will be raised in logs.
By default, exception will be raised for entire process.
### Splitting lines
In the case if you have 2 different records in one line and there is a split index you can add a method in your entity that should return index of the next record and mark it with annotation `SplitLineAfter`.
For example record
```
HEADERMy Title 26 EmplJoe1 Smith Developer 07500010012009
```
Number 26 indicates index of the next record.
You can describe it with entity:
```java
@FixedLine(startsWith = "HEADER")
public class HeaderSplit {
@FixedField(offset = 7, length = 10)
public String title;
@FixedField(offset = 17, length = 2)
public int headerLength;
@SplitLineAfter
public int getSplitIndex() {
return headerLength;
}
}
```
### Custom rules for mixed lines
There is a `startsWith` parameter for easy-to-use identifying the class to deserialize, but sometimes it is not enough. So there is a `predicate` parameter in `FixedLine` annotation where you should pass your own custom rule as predicate. Just implement `Predicate` and pass pointer to class in annotation.
```java
@FixedLine(predicate = EmployeePositionPredicate.class)
```
This class will be initialized just once and cached.
### Handling empty fields during formatting
When formatting an object back to a fixed length string, you can control how empty (null) fields are handled using the `fallbackStringForNullValue` parameter in the `FixedField` annotation. If a field's value is `null`, the formatter will replace it with the specified fallback string. If the fallback string is shorter than the defined field length, it will be padded according to the specified alignment and padding character.
Let's say we have a class defined as follows:
```java
public class Employee {
@FixedField(offset = 1, length = 10, align = Align.LEFT)
public String firstName;
@FixedField(offset = 10, length = 10, align = Align.LEFT)
public String lastName;
@FixedField(offset = 20, length = 10, align = Align.LEFT)
public String role;
@FixedField(offset = 20, length = 10, align = Align.LEFT, ignore = "0{8}")
public LocalDate joinDate;
}
```
When parsing the following lines, there will be two `null` values for the 2nd line: `lastName` and `joinDate`:
```
Joe1 Smith Developer 12122009
Joe3 Tester 00000000
```
However, when formatting the resulting object back to a fixed length string, the `null` values for these columns would not be included, leading to a string like this:
```
Joe3 Tester
```
As this is most likely not what we want, we can specify a fallback string for each of the columns as follows:
```java
public class Employee {
@FixedField(offset = 1, length = 10, align = Align.LEFT, fallbackStringForNullValue = " ")
public String firstName;
@FixedField(offset = 10, length = 10, align = Align.LEFT, fallbackStringForNullValue = " ")
public String lastName;
@FixedField(offset = 20, length = 10, align = Align.LEFT, fallbackStringForNullValue = " ")
public String role;
@FixedField(offset = 20, length = 10, align = Align.LEFT, ignore = "0{8}", fallbackStringForNullValue = "00000000")
public LocalDate joinDate;
}
```
When formatting the object back into a fixed length string, the resulting string will not look as expected (note how the fallback-strings will be auto-padded, if needed):
```
Joe3 Tester 00000000
```
## Java records support
There is an experimental support of Java 14+ records with no breaking of Java 8 support.
Just annotate record's constructor as follows:
```java
record Employee (
@FixedField(offset = 1, length = 10, align = Align.LEFT)
String firstName,
@FixedField(offset = 10, length = 10, align = Align.LEFT)
String lastName
){}
```
and it works the same way as annotated class.
## Benchmark
There is a benchmark, you can run it with `gradle jmh` command. Also, you can change running parameters of it in file `src/jmh/java/name/velikodniy/vitaliy/fixedlength/benchmark/BenchmarkRunner.java`.