FIT (Flexible and Interoperable Data Transfer) is a binary file format designed specifically for fitness and activity tracking devices. This guide covers reading, writing, and analyzing FIT files in Java.
Understanding FIT Files
What are FIT Files?
- Binary format developed by Garmin for fitness devices
- Stores activity, monitoring, and device data
- Highly efficient with small file sizes
- Used by Garmin, Strava, and other fitness platforms
Common FIT File Types:
- Activity files: Workouts, runs, cycling sessions
- Monitoring files: Daily activity, sleep, heart rate
- Settings files: Device configuration
- Courses: Planned routes
Dependencies and Setup
Maven Dependencies
<properties>
<garmin-fit.version>21.115.0</garmin-fit.version>
<java-sdk.version>21.115.0</java-sdk.version>
</properties>
<dependencies>
<!-- Garmin FIT SDK -->
<dependency>
<groupId>com.garmin</groupId>
<artifactId>fit</artifactId>
<version>${garmin-fit.version}</version>
</dependency>
<!-- Java SDK for FIT processing -->
<dependency>
<groupId>com.garmin</groupId>
<artifactId>java-sdk</artifactId>
<version>${java-sdk.version}</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
FIT SDK Setup
# Download FIT SDK from Garmin developer site # Extract and include in your project
Core FIT File Processing
1. Basic FIT File Reader
import com.garmin.fit.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class BasicFitReader implements MesgListener, MesgDefinitionListener {
private List<RecordMesg> records = new ArrayList<>();
private List<LapMesg> laps = new ArrayList<>();
private SessionMesg session;
private ActivityMesg activity;
public FitData readFitFile(String filePath) throws IOException, FitRuntimeException {
FileInputStream fileStream = null;
Decode decode = new Decode();
FitData fitData = new FitData();
try {
fileStream = new FileInputStream(filePath);
if (!decode.checkFileIntegrity(fileStream)) {
throw new IOException("FIT file integrity check failed");
}
fileStream.close();
fileStream = new FileInputStream(filePath);
// Read the file
decode.addListener((MesgDefinitionListener) this);
decode.addListener((MesgListener) this);
decode.read(fileStream);
// Populate result object
fitData.setRecords(records);
fitData.setLaps(laps);
fitData.setSession(session);
fitData.setActivity(activity);
return fitData;
} finally {
if (fileStream != null) {
fileStream.close();
}
}
}
@Override
public void onMesg(Mesg mesg) {
switch (mesg.getNum()) {
case MesgNum.RECORD:
records.add(new RecordMesg(mesg));
break;
case MesgNum.LAP:
laps.add(new LapMesg(mesg));
break;
case MesgNum.SESSION:
session = new SessionMesg(mesg);
break;
case MesgNum.ACTIVITY:
activity = new ActivityMesg(mesg);
break;
}
}
@Override
public void onMesgDefinition(MesgDefinition mesgDef) {
// Handle message definitions if needed
}
}
public class FitData {
private List<RecordMesg> records;
private List<LapMesg> laps;
private SessionMesg session;
private ActivityMesg activity;
private FileIdMesg fileId;
// Getters and setters
public List<RecordMesg> getRecords() { return records; }
public void setRecords(List<RecordMesg> records) { this.records = records; }
public List<LapMesg> getLaps() { return laps; }
public void setLaps(List<LapMesg> laps) { this.laps = laps; }
public SessionMesg getSession() { return session; }
public void setSession(SessionMesg session) { this.session = session; }
public ActivityMesg getActivity() { return activity; }
public void setActivity(ActivityMesg activity) { this.activity = activity; }
public FileIdMesg getFileId() { return fileId; }
public void setFileId(FileIdMesg fileId) { this.fileId = fileId; }
}
2. Advanced FIT File Processor
import com.garmin.fit.*;
import java.io.FileInputStream;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
public class AdvancedFitProcessor implements MesgListener {
private FitAnalysisResult analysisResult = new FitAnalysisResult();
private List<RecordMesg> records = new ArrayList<>();
private List<LapMesg> laps = new ArrayList<>();
private SessionMesg session;
private ActivityMesg activity;
private FileIdMesg fileId;
public FitAnalysisResult analyzeFitFile(String filePath) throws Exception {
FileInputStream fileStream = new FileInputStream(filePath);
Decode decode = new Decode();
decode.addListener(this);
decode.read(fileStream);
fileStream.close();
// Perform analysis
analyzeRecords();
analyzeLaps();
analyzeSession();
return analysisResult;
}
@Override
public void onMesg(Mesg mesg) {
switch (mesg.getNum()) {
case MesgNum.FILE_ID:
fileId = new FileIdMesg(mesg);
break;
case MesgNum.RECORD:
RecordMesg record = new RecordMesg(mesg);
records.add(record);
break;
case MesgNum.LAP:
LapMesg lap = new LapMesg(mesg);
laps.add(lap);
break;
case MesgNum.SESSION:
session = new SessionMesg(mesg);
break;
case MesgNum.ACTIVITY:
activity = new ActivityMesg(mesg);
break;
}
}
private void analyzeRecords() {
if (records.isEmpty()) return;
List<Double> heartRates = new ArrayList<>();
List<Double> altitudes = new ArrayList<>();
List<Double> speeds = new ArrayList<>();
List<Double> distances = new ArrayList<>();
List<Double> cadences = new ArrayList<>();
List<Double> temperatures = new ArrayList<>();
for (RecordMesg record : records) {
// Heart Rate
if (record.getHeartRate() != null) {
heartRates.add(record.getHeartRate().doubleValue());
}
// Altitude
if (record.getAltitude() != null) {
altitudes.add(record.getAltitude());
}
// Speed
if (record.getSpeed() != null) {
speeds.add(record.getSpeed());
}
// Distance
if (record.getDistance() != null) {
distances.add(record.getDistance());
}
// Cadence
if (record.getCadence() != null) {
cadences.add(record.getCadence().doubleValue());
}
// Temperature
if (record.getTemperature() != null) {
temperatures.add(record.getTemperature().doubleValue());
}
}
analysisResult.setHeartRates(heartRates);
analysisResult.setAltitudes(altitudes);
analysisResult.setSpeeds(speeds);
analysisResult.setDistances(distances);
analysisResult.setCadences(cadences);
analysisResult.setTemperatures(temperatures);
// Calculate statistics
calculateStatistics();
}
private void calculateStatistics() {
// Heart Rate Stats
if (!analysisResult.getHeartRates().isEmpty()) {
analysisResult.setAvgHeartRate(analysisResult.getHeartRates().stream()
.mapToDouble(Double::doubleValue).average().orElse(0.0));
analysisResult.setMaxHeartRate(analysisResult.getHeartRates().stream()
.mapToDouble(Double::doubleValue).max().orElse(0.0));
analysisResult.setMinHeartRate(analysisResult.getHeartRates().stream()
.mapToDouble(Double::doubleValue).min().orElse(0.0));
}
// Speed Stats
if (!analysisResult.getSpeeds().isEmpty()) {
analysisResult.setAvgSpeed(analysisResult.getSpeeds().stream()
.mapToDouble(Double::doubleValue).average().orElse(0.0));
analysisResult.setMaxSpeed(analysisResult.getSpeeds().stream()
.mapToDouble(Double::doubleValue).max().orElse(0.0));
}
// Cadence Stats
if (!analysisResult.getCadences().isEmpty()) {
analysisResult.setAvgCadence(analysisResult.getCadences().stream()
.mapToDouble(Double::doubleValue).average().orElse(0.0));
analysisResult.setMaxCadence(analysisResult.getCadences().stream()
.mapToDouble(Double::doubleValue).max().orElse(0.0));
}
// Total Distance
if (!analysisResult.getDistances().isEmpty()) {
analysisResult.setTotalDistance(analysisResult.getDistances().stream()
.mapToDouble(Double::doubleValue).max().orElse(0.0));
}
// Elevation Gain
if (!analysisResult.getAltitudes().isEmpty()) {
analysisResult.setElevationGain(calculateElevationGain(analysisResult.getAltitudes()));
}
}
private double calculateElevationGain(List<Double> altitudes) {
double gain = 0.0;
double previousAltitude = altitudes.get(0);
for (Double altitude : altitudes) {
if (altitude > previousAltitude) {
gain += (altitude - previousAltitude);
}
previousAltitude = altitude;
}
return gain;
}
private void analyzeLaps() {
if (laps.isEmpty()) return;
List<LapAnalysis> lapAnalyses = new ArrayList<>();
for (int i = 0; i < laps.size(); i++) {
LapMesg lap = laps.get(i);
LapAnalysis analysis = new LapAnalysis();
analysis.setLapNumber(i + 1);
analysis.setStartTime(convertFitTimestamp(lap.getStartTime()));
analysis.setTotalElapsedTime(lap.getTotalElapsedTime());
analysis.setTotalTimerTime(lap.getTotalTimerTime());
analysis.setTotalDistance(lap.getTotalDistance());
if (lap.getAvgSpeed() != null) {
analysis.setAvgSpeed(lap.getAvgSpeed());
}
if (lap.getMaxSpeed() != null) {
analysis.setMaxSpeed(lap.getMaxSpeed());
}
if (lap.getAvgHeartRate() != null) {
analysis.setAvgHeartRate(lap.getAvgHeartRate());
}
if (lap.getMaxHeartRate() != null) {
analysis.setMaxHeartRate(lap.getMaxHeartRate());
}
if (lap.getAvgCadence() != null) {
analysis.setAvgCadence(lap.getAvgCadence());
}
if (lap.getMaxCadence() != null) {
analysis.setMaxCadence(lap.getMaxCadence());
}
lapAnalyses.add(analysis);
}
analysisResult.setLapAnalyses(lapAnalyses);
}
private void analyzeSession() {
if (session == null) return;
SessionAnalysis sessionAnalysis = new SessionAnalysis();
sessionAnalysis.setSport(session.getSport().name());
sessionAnalysis.setSubSport(session.getSubSport().name());
sessionAnalysis.setStartTime(convertFitTimestamp(session.getStartTime()));
sessionAnalysis.setTotalElapsedTime(session.getTotalElapsedTime());
sessionAnalysis.setTotalTimerTime(session.getTotalTimerTime());
sessionAnalysis.setTotalDistance(session.getTotalDistance());
sessionAnalysis.setTotalCalories(session.getTotalCalories());
if (session.getAvgSpeed() != null) {
sessionAnalysis.setAvgSpeed(session.getAvgSpeed());
}
if (session.getMaxSpeed() != null) {
sessionAnalysis.setMaxSpeed(session.getMaxSpeed());
}
if (session.getAvgHeartRate() != null) {
sessionAnalysis.setAvgHeartRate(session.getAvgHeartRate());
}
if (session.getMaxHeartRate() != null) {
sessionAnalysis.setMaxHeartRate(session.getMaxHeartRate());
}
if (session.getAvgCadence() != null) {
sessionAnalysis.setAvgCadence(session.getAvgCadence());
}
if (session.getMaxCadence() != null) {
sessionAnalysis.setMaxCadence(session.getMaxCadence());
}
if (session.getTotalAscent() != null) {
sessionAnalysis.setTotalAscent(session.getTotalAscent());
}
if (session.getTotalDescent() != null) {
sessionAnalysis.setTotalDescent(session.getTotalDescent());
}
analysisResult.setSessionAnalysis(sessionAnalysis);
}
private ZonedDateTime convertFitTimestamp(DateTime timestamp) {
if (timestamp == null) return null;
Instant instant = Instant.ofEpochSecond(timestamp.getTimestamp());
return ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
}
}
3. Data Model Classes
public class FitAnalysisResult {
private List<Double> heartRates = new ArrayList<>();
private List<Double> altitudes = new ArrayList<>();
private List<Double> speeds = new ArrayList<>();
private List<Double> distances = new ArrayList<>();
private List<Double> cadences = new ArrayList<>();
private List<Double> temperatures = new ArrayList<>();
private double avgHeartRate;
private double maxHeartRate;
private double minHeartRate;
private double avgSpeed;
private double maxSpeed;
private double avgCadence;
private double maxCadence;
private double totalDistance;
private double elevationGain;
private List<LapAnalysis> lapAnalyses = new ArrayList<>();
private SessionAnalysis sessionAnalysis;
// Getters and setters
public List<Double> getHeartRates() { return heartRates; }
public void setHeartRates(List<Double> heartRates) { this.heartRates = heartRates; }
public List<Double> getAltitudes() { return altitudes; }
public void setAltitudes(List<Double> altitudes) { this.altitudes = altitudes; }
public List<Double> getSpeeds() { return speeds; }
public void setSpeeds(List<Double> speeds) { this.speeds = speeds; }
public List<Double> getDistances() { return distances; }
public void setDistances(List<Double> distances) { this.distances = distances; }
public List<Double> getCadences() { return cadences; }
public void setCadences(List<Double> cadences) { this.cadences = cadences; }
public List<Double> getTemperatures() { return temperatures; }
public void setTemperatures(List<Double> temperatures) { this.temperatures = temperatures; }
public double getAvgHeartRate() { return avgHeartRate; }
public void setAvgHeartRate(double avgHeartRate) { this.avgHeartRate = avgHeartRate; }
public double getMaxHeartRate() { return maxHeartRate; }
public void setMaxHeartRate(double maxHeartRate) { this.maxHeartRate = maxHeartRate; }
public double getMinHeartRate() { return minHeartRate; }
public void setMinHeartRate(double minHeartRate) { this.minHeartRate = minHeartRate; }
public double getAvgSpeed() { return avgSpeed; }
public void setAvgSpeed(double avgSpeed) { this.avgSpeed = avgSpeed; }
public double getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(double maxSpeed) { this.maxSpeed = maxSpeed; }
public double getAvgCadence() { return avgCadence; }
public void setAvgCadence(double avgCadence) { this.avgCadence = avgCadence; }
public double getMaxCadence() { return maxCadence; }
public void setMaxCadence(double maxCadence) { this.maxCadence = maxCadence; }
public double getTotalDistance() { return totalDistance; }
public void setTotalDistance(double totalDistance) { this.totalDistance = totalDistance; }
public double getElevationGain() { return elevationGain; }
public void setElevationGain(double elevationGain) { this.elevationGain = elevationGain; }
public List<LapAnalysis> getLapAnalyses() { return lapAnalyses; }
public void setLapAnalyses(List<LapAnalysis> lapAnalyses) { this.lapAnalyses = lapAnalyses; }
public SessionAnalysis getSessionAnalysis() { return sessionAnalysis; }
public void setSessionAnalysis(SessionAnalysis sessionAnalysis) { this.sessionAnalysis = sessionAnalysis; }
public void printSummary() {
System.out.println("=== FIT File Analysis Summary ===");
System.out.printf("Total Distance: %.2f meters%n", totalDistance);
System.out.printf("Average Speed: %.2f m/s%n", avgSpeed);
System.out.printf("Max Speed: %.2f m/s%n", maxSpeed);
System.out.printf("Average Heart Rate: %.1f bpm%n", avgHeartRate);
System.out.printf("Max Heart Rate: %.1f bpm%n", maxHeartRate);
System.out.printf("Elevation Gain: %.1f meters%n", elevationGain);
System.out.printf("Number of Laps: %d%n", lapAnalyses.size());
System.out.printf("Number of Data Points: %d%n", heartRates.size());
}
}
public class LapAnalysis {
private int lapNumber;
private ZonedDateTime startTime;
private Double totalElapsedTime;
private Double totalTimerTime;
private Double totalDistance;
private Double avgSpeed;
private Double maxSpeed;
private Short avgHeartRate;
private Short maxHeartRate;
private Short avgCadence;
private Short maxCadence;
// Getters and setters
public int getLapNumber() { return lapNumber; }
public void setLapNumber(int lapNumber) { this.lapNumber = lapNumber; }
public ZonedDateTime getStartTime() { return startTime; }
public void setStartTime(ZonedDateTime startTime) { this.startTime = startTime; }
public Double getTotalElapsedTime() { return totalElapsedTime; }
public void setTotalElapsedTime(Double totalElapsedTime) { this.totalElapsedTime = totalElapsedTime; }
public Double getTotalTimerTime() { return totalTimerTime; }
public void setTotalTimerTime(Double totalTimerTime) { this.totalTimerTime = totalTimerTime; }
public Double getTotalDistance() { return totalDistance; }
public void setTotalDistance(Double totalDistance) { this.totalDistance = totalDistance; }
public Double getAvgSpeed() { return avgSpeed; }
public void setAvgSpeed(Double avgSpeed) { this.avgSpeed = avgSpeed; }
public Double getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(Double maxSpeed) { this.maxSpeed = maxSpeed; }
public Short getAvgHeartRate() { return avgHeartRate; }
public void setAvgHeartRate(Short avgHeartRate) { this.avgHeartRate = avgHeartRate; }
public Short getMaxHeartRate() { return maxHeartRate; }
public void setMaxHeartRate(Short maxHeartRate) { this.maxHeartRate = maxHeartRate; }
public Short getAvgCadence() { return avgCadence; }
public void setAvgCadence(Short avgCadence) { this.avgCadence = avgCadence; }
public Short getMaxCadence() { return maxCadence; }
public void setMaxCadence(Short maxCadence) { this.maxCadence = maxCadence; }
}
public class SessionAnalysis {
private String sport;
private String subSport;
private ZonedDateTime startTime;
private Double totalElapsedTime;
private Double totalTimerTime;
private Double totalDistance;
private Integer totalCalories;
private Double avgSpeed;
private Double maxSpeed;
private Short avgHeartRate;
private Short maxHeartRate;
private Short avgCadence;
private Short maxCadence;
private Integer totalAscent;
private Integer totalDescent;
// Getters and setters
public String getSport() { return sport; }
public void setSport(String sport) { this.sport = sport; }
public String getSubSport() { return subSport; }
public void setSubSport(String subSport) { this.subSport = subSport; }
public ZonedDateTime getStartTime() { return startTime; }
public void setStartTime(ZonedDateTime startTime) { this.startTime = startTime; }
public Double getTotalElapsedTime() { return totalElapsedTime; }
public void setTotalElapsedTime(Double totalElapsedTime) { this.totalElapsedTime = totalElapsedTime; }
public Double getTotalTimerTime() { return totalTimerTime; }
public void setTotalTimerTime(Double totalTimerTime) { this.totalTimerTime = totalTimerTime; }
public Double getTotalDistance() { return totalDistance; }
public void setTotalDistance(Double totalDistance) { this.totalDistance = totalDistance; }
public Integer getTotalCalories() { return totalCalories; }
public void setTotalCalories(Integer totalCalories) { this.totalCalories = totalCalories; }
public Double getAvgSpeed() { return avgSpeed; }
public void setAvgSpeed(Double avgSpeed) { this.avgSpeed = avgSpeed; }
public Double getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(Double maxSpeed) { this.maxSpeed = maxSpeed; }
public Short getAvgHeartRate() { return avgHeartRate; }
public void setAvgHeartRate(Short avgHeartRate) { this.avgHeartRate = avgHeartRate; }
public Short getMaxHeartRate() { return maxHeartRate; }
public void setMaxHeartRate(Short maxHeartRate) { this.maxHeartRate = maxHeartRate; }
public Short getAvgCadence() { return avgCadence; }
public void setAvgCadence(Short avgCadence) { this.avgCadence = avgCadence; }
public Short getMaxCadence() { return maxCadence; }
public void setMaxCadence(Short maxCadence) { this.maxCadence = maxCadence; }
public Integer getTotalAscent() { return totalAscent; }
public void setTotalAscent(Integer totalAscent) { this.totalAscent = totalAscent; }
public Integer getTotalDescent() { return totalDescent; }
public void setTotalDescent(Integer totalDescent) { this.totalDescent = totalDescent; }
}
FIT File Writing
1. FIT File Creator
import com.garmin.fit.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
public class FitFileCreator {
private FileOutputStream fileOutputStream;
private Encode encode;
private MesgBroadcaster mesgBroadcaster;
public void createActivityFile(String outputPath, ActivityData activityData) throws IOException {
try {
fileOutputStream = new FileOutputStream(outputPath);
encode = new Encode(ProtocolVersion.V2_0);
mesgBroadcaster = new MesgBroadcaster(encode);
// Open the file
encode.open(fileOutputStream);
// Write required messages
writeFileId();
writeActivity(activityData);
writeSession(activityData);
writeLaps(activityData.getLaps());
writeRecords(activityData.getRecords());
// Close the file
encode.close();
} finally {
if (fileOutputStream != null) {
fileOutputStream.close();
}
}
}
private void writeFileId() {
FileIdMesg fileIdMesg = new FileIdMesg();
fileIdMesg.setType(File.ACTIVITY);
fileIdMesg.setManufacturer(Manufacturer.GARMIN);
fileIdMesg.setProduct(1);
fileIdMesg.setSerialNumber(12345L);
fileIdMesg.setTimeCreated(new DateTime(new Date()));
mesgBroadcaster.broadcast(fileIdMesg);
}
private void writeActivity(ActivityData activityData) {
ActivityMesg activityMesg = new ActivityMesg();
activityMesg.setTimestamp(new DateTime(activityData.getStartTime()));
activityMesg.setTotalTimerTime(activityData.getTotalTime());
activityMesg.setNumSessions(1);
activityMesg.setType(Activity.MANUAL);
activityMesg.setEvent(Event.ACTIVITY);
activityMesg.setEventType(EventType.STOP);
mesgBroadcaster.broadcast(activityMesg);
}
private void writeSession(ActivityData activityData) {
SessionMesg sessionMesg = new SessionMesg();
sessionMesg.setTimestamp(new DateTime(activityData.getStartTime()));
sessionMesg.setStartTime(new DateTime(activityData.getStartTime()));
sessionMesg.setTotalElapsedTime(activityData.getTotalTime());
sessionMesg.setTotalTimerTime(activityData.getTotalTime());
sessionMesg.setTotalDistance(activityData.getTotalDistance());
sessionMesg.setTotalCalories(activityData.getTotalCalories());
sessionMesg.setSport(activityData.getSport());
sessionMesg.setSubSport(activityData.getSubSport());
sessionMesg.setFirstLapIndex(0L);
sessionMesg.setNumLaps(activityData.getLaps().size());
sessionMesg.setEvent(Event.SESSION);
sessionMesg.setEventType(EventType.STOP);
mesgBroadcaster.broadcast(sessionMesg);
}
private void writeLaps(List<LapData> laps) {
for (int i = 0; i < laps.size(); i++) {
LapData lapData = laps.get(i);
LapMesg lapMesg = new LapMesg();
lapMesg.setTimestamp(new DateTime(lapData.getEndTime()));
lapMesg.setStartTime(new DateTime(lapData.getStartTime()));
lapMesg.setTotalElapsedTime(lapData.getTotalTime());
lapMesg.setTotalTimerTime(lapData.getTotalTime());
lapMesg.setTotalDistance(lapData.getDistance());
lapMesg.setTotalCalories(lapData.getCalories());
lapMesg.setEvent(Event.LAP);
lapMesg.setEventType(EventType.STOP);
lapMesg.setLapTrigger(LapTrigger.SESSION_END);
if (lapData.getAvgHeartRate() != null) {
lapMesg.setAvgHeartRate(lapData.getAvgHeartRate());
}
if (lapData.getMaxHeartRate() != null) {
lapMesg.setMaxHeartRate(lapData.getMaxHeartRate());
}
if (lapData.getAvgSpeed() != null) {
lapMesg.setAvgSpeed(lapData.getAvgSpeed());
}
if (lapData.getMaxSpeed() != null) {
lapMesg.setMaxSpeed(lapData.getMaxSpeed());
}
mesgBroadcaster.broadcast(lapMesg);
}
}
private void writeRecords(List<RecordData> records) {
for (RecordData recordData : records) {
RecordMesg recordMesg = new RecordMesg();
recordMesg.setTimestamp(new DateTime(recordData.getTimestamp()));
if (recordData.getPositionLat() != null && recordData.getPositionLong() != null) {
recordMesg.setPositionLat(recordData.getPositionLat());
recordMesg.setPositionLong(recordData.getPositionLong());
}
if (recordData.getDistance() != null) {
recordMesg.setDistance(recordData.getDistance());
}
if (recordData.getAltitude() != null) {
recordMesg.setAltitude(recordData.getAltitude());
}
if (recordData.getSpeed() != null) {
recordMesg.setSpeed(recordData.getSpeed());
}
if (recordData.getHeartRate() != null) {
recordMesg.setHeartRate(recordData.getHeartRate());
}
if (recordData.getCadence() != null) {
recordMesg.setCadence(recordData.getCadence());
}
if (recordData.getTemperature() != null) {
recordMesg.setTemperature(recordData.getTemperature());
}
mesgBroadcaster.broadcast(recordMesg);
}
}
}
public class ActivityData {
private ZonedDateTime startTime;
private double totalTime;
private double totalDistance;
private int totalCalories;
private Sport sport;
private SubSport subSport;
private List<LapData> laps;
private List<RecordData> records;
// Getters and setters
public ZonedDateTime getStartTime() { return startTime; }
public void setStartTime(ZonedDateTime startTime) { this.startTime = startTime; }
public double getTotalTime() { return totalTime; }
public void setTotalTime(double totalTime) { this.totalTime = totalTime; }
public double getTotalDistance() { return totalDistance; }
public void setTotalDistance(double totalDistance) { this.totalDistance = totalDistance; }
public int getTotalCalories() { return totalCalories; }
public void setTotalCalories(int totalCalories) { this.totalCalories = totalCalories; }
public Sport getSport() { return sport; }
public void setSport(Sport sport) { this.sport = sport; }
public SubSport getSubSport() { return subSport; }
public void setSubSport(SubSport subSport) { this.subSport = subSport; }
public List<LapData> getLaps() { return laps; }
public void setLaps(List<LapData> laps) { this.laps = laps; }
public List<RecordData> getRecords() { return records; }
public void setRecords(List<RecordData> records) { this.records = records; }
}
public class LapData {
private ZonedDateTime startTime;
private ZonedDateTime endTime;
private double totalTime;
private double distance;
private int calories;
private Short avgHeartRate;
private Short maxHeartRate;
private Double avgSpeed;
private Double maxSpeed;
// Getters and setters
public ZonedDateTime getStartTime() { return startTime; }
public void setStartTime(ZonedDateTime startTime) { this.startTime = startTime; }
public ZonedDateTime getEndTime() { return endTime; }
public void setEndTime(ZonedDateTime endTime) { this.endTime = endTime; }
public double getTotalTime() { return totalTime; }
public void setTotalTime(double totalTime) { this.totalTime = totalTime; }
public double getDistance() { return distance; }
public void setDistance(double distance) { this.distance = distance; }
public int getCalories() { return calories; }
public void setCalories(int calories) { this.calories = calories; }
public Short getAvgHeartRate() { return avgHeartRate; }
public void setAvgHeartRate(Short avgHeartRate) { this.avgHeartRate = avgHeartRate; }
public Short getMaxHeartRate() { return maxHeartRate; }
public void setMaxHeartRate(Short maxHeartRate) { this.maxHeartRate = maxHeartRate; }
public Double getAvgSpeed() { return avgSpeed; }
public void setAvgSpeed(Double avgSpeed) { this.avgSpeed = avgSpeed; }
public Double getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(Double maxSpeed) { this.maxSpeed = maxSpeed; }
}
public class RecordData {
private ZonedDateTime timestamp;
private Integer positionLat;
private Integer positionLong;
private Double distance;
private Double altitude;
private Double speed;
private Short heartRate;
private Short cadence;
private Byte temperature;
// Getters and setters
public ZonedDateTime getTimestamp() { return timestamp; }
public void setTimestamp(ZonedDateTime timestamp) { this.timestamp = timestamp; }
public Integer getPositionLat() { return positionLat; }
public void setPositionLat(Integer positionLat) { this.positionLat = positionLat; }
public Integer getPositionLong() { return positionLong; }
public void setPositionLong(Integer positionLong) { this.positionLong = positionLong; }
public Double getDistance() { return distance; }
public void setDistance(Double distance) { this.distance = distance; }
public Double getAltitude() { return altitude; }
public void setAltitude(Double altitude) { this.altitude = altitude; }
public Double getSpeed() { return speed; }
public void setSpeed(Double speed) { this.speed = speed; }
public Short getHeartRate() { return heartRate; }
public void setHeartRate(Short heartRate) { this.heartRate = heartRate; }
public Short getCadence() { return cadence; }
public void setCadence(Short cadence) { this.cadence = cadence; }
public Byte getTemperature() { return temperature; }
public void setTemperature(Byte temperature) { this.temperature = temperature; }
}
GPS Data Processing
1. GPS Track Analyzer
import com.garmin.fit.*;
import java.util.*;
public class GpsTrackAnalyzer {
public GpsAnalysis analyzeGpsTrack(List<RecordMesg> records) {
GpsAnalysis analysis = new GpsAnalysis();
if (records.isEmpty()) {
return analysis;
}
List<GpsPoint> gpsPoints = extractGpsPoints(records);
analysis.setGpsPoints(gpsPoints);
analysis.setTotalPoints(gpsPoints.size());
if (gpsPoints.size() > 1) {
calculateTrackStatistics(analysis, gpsPoints);
calculateSpeedProfile(analysis, gpsPoints);
calculateElevationProfile(analysis, gpsPoints);
}
return analysis;
}
private List<GpsPoint> extractGpsPoints(List<RecordMesg> records) {
List<GpsPoint> points = new ArrayList<>();
for (RecordMesg record : records) {
if (record.getPositionLat() != null && record.getPositionLong() != null) {
GpsPoint point = new GpsPoint();
point.setTimestamp(record.getTimestamp());
point.setLatitude(semicirclesToDegrees(record.getPositionLat()));
point.setLongitude(semicirclesToDegrees(record.getPositionLong()));
if (record.getAltitude() != null) {
point.setAltitude(record.getAltitude());
}
if (record.getSpeed() != null) {
point.setSpeed(record.getSpeed());
}
if (record.getHeartRate() != null) {
point.setHeartRate(record.getHeartRate());
}
if (record.getCadence() != null) {
point.setCadence(record.getCadence());
}
points.add(point);
}
}
return points;
}
private double semicirclesToDegrees(Integer semicircles) {
return semicircles * (180.0 / Math.pow(2, 31));
}
private void calculateTrackStatistics(GpsAnalysis analysis, List<GpsPoint> points) {
double totalDistance = 0.0;
double maxSpeed = 0.0;
double minAltitude = Double.MAX_VALUE;
double maxAltitude = Double.MIN_VALUE;
GpsPoint previousPoint = points.get(0);
for (int i = 1; i < points.size(); i++) {
GpsPoint currentPoint = points.get(i);
// Calculate distance between points
double distance = calculateDistance(previousPoint, currentPoint);
totalDistance += distance;
// Update max speed
if (currentPoint.getSpeed() != null && currentPoint.getSpeed() > maxSpeed) {
maxSpeed = currentPoint.getSpeed();
}
// Update altitude extremes
if (currentPoint.getAltitude() != null) {
minAltitude = Math.min(minAltitude, currentPoint.getAltitude());
maxAltitude = Math.max(maxAltitude, currentPoint.getAltitude());
}
previousPoint = currentPoint;
}
analysis.setTotalDistance(totalDistance);
analysis.setMaxSpeed(maxSpeed);
analysis.setMinAltitude(minAltitude == Double.MAX_VALUE ? 0 : minAltitude);
analysis.setMaxAltitude(maxAltitude == Double.MIN_VALUE ? 0 : maxAltitude);
analysis.setElevationGain(calculateElevationGain(points));
}
private double calculateDistance(GpsPoint p1, GpsPoint p2) {
final int R = 6371000; // Earth radius in meters
double lat1 = Math.toRadians(p1.getLatitude());
double lon1 = Math.toRadians(p1.getLongitude());
double lat2 = Math.toRadians(p2.getLatitude());
double lon2 = Math.toRadians(p2.getLongitude());
double dLat = lat2 - lat1;
double dLon = lon2 - lon1;
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(dLon/2) * Math.sin(dLon/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
private double calculateElevationGain(List<GpsPoint> points) {
double gain = 0.0;
Double previousAltitude = null;
for (GpsPoint point : points) {
if (point.getAltitude() != null) {
if (previousAltitude != null && point.getAltitude() > previousAltitude) {
gain += (point.getAltitude() - previousAltitude);
}
previousAltitude = point.getAltitude();
}
}
return gain;
}
private void calculateSpeedProfile(GpsAnalysis analysis, List<GpsPoint> points) {
List<Double> speeds = new ArrayList<>();
for (GpsPoint point : points) {
if (point.getSpeed() != null) {
speeds.add(point.getSpeed());
}
}
if (!speeds.isEmpty()) {
double avgSpeed = speeds.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
analysis.setAvgSpeed(avgSpeed);
}
}
private void calculateElevationProfile(GpsAnalysis analysis, List<GpsPoint> points) {
List<Double> elevations = new ArrayList<>();
for (GpsPoint point : points) {
if (point.getAltitude() != null) {
elevations.add(point.getAltitude());
}
}
analysis.setElevationProfile(elevations);
}
}
public class GpsAnalysis {
private List<GpsPoint> gpsPoints;
private int totalPoints;
private double totalDistance;
private double avgSpeed;
private double maxSpeed;
private double minAltitude;
private double maxAltitude;
private double elevationGain;
private List<Double> elevationProfile;
// Getters and setters
public List<GpsPoint> getGpsPoints() { return gpsPoints; }
public void setGpsPoints(List<GpsPoint> gpsPoints) { this.gpsPoints = gpsPoints; }
public int getTotalPoints() { return totalPoints; }
public void setTotalPoints(int totalPoints) { this.totalPoints = totalPoints; }
public double getTotalDistance() { return totalDistance; }
public void setTotalDistance(double totalDistance) { this.totalDistance = totalDistance; }
public double getAvgSpeed() { return avgSpeed; }
public void setAvgSpeed(double avgSpeed) { this.avgSpeed = avgSpeed; }
public double getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(double maxSpeed) { this.maxSpeed = maxSpeed; }
public double getMinAltitude() { return minAltitude; }
public void setMinAltitude(double minAltitude) { this.minAltitude = minAltitude; }
public double getMaxAltitude() { return maxAltitude; }
public void setMaxAltitude(double maxAltitude) { this.maxAltitude = maxAltitude; }
public double getElevationGain() { return elevationGain; }
public void setElevationGain(double elevationGain) { this.elevationGain = elevationGain; }
public List<Double> getElevationProfile() { return elevationProfile; }
public void setElevationProfile(List<Double> elevationProfile) { this.elevationProfile = elevationProfile; }
}
public class GpsPoint {
private DateTime timestamp;
private double latitude;
private double longitude;
private Double altitude;
private Double speed;
private Short heartRate;
private Short cadence;
// Getters and setters
public DateTime getTimestamp() { return timestamp; }
public void setTimestamp(DateTime timestamp) { this.timestamp = timestamp; }
public double getLatitude() { return latitude; }
public void setLatitude(double latitude) { this.latitude = latitude; }
public double getLongitude() { return longitude; }
public void setLongitude(double longitude) { this.longitude = longitude; }
public Double getAltitude() { return altitude; }
public void setAltitude(Double altitude) { this.altitude = altitude; }
public Double getSpeed() { return speed; }
public void setSpeed(Double speed) { this.speed = speed; }
public Short getHeartRate() { return heartRate; }
public void setHeartRate(Short heartRate) { this.heartRate = heartRate; }
public Short getCadence() { return cadence; }
public void setCadence(Short cadence) { this.cadence = cadence; }
}
Usage Examples
1. Basic FIT File Reading
public class FitReaderExample {
public static void main(String[] args) {
try {
String fitFilePath = "path/to/activity.fit";
// Basic reading
BasicFitReader reader = new BasicFitReader();
FitData fitData = reader.readFitFile(fitFilePath);
System.out.println("Records: " + fitData.getRecords().size());
System.out.println("Laps: " + fitData.getLaps().size());
if (fitData.getSession() != null) {
System.out.println("Sport: " + fitData.getSession().getSport());
System.out.println("Distance: " + fitData.getSession().getTotalDistance());
}
// Advanced analysis
AdvancedFitProcessor processor = new AdvancedFitProcessor();
FitAnalysisResult analysis = processor.analyzeFitFile(fitFilePath);
analysis.printSummary();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. Creating FIT Files
public class FitCreatorExample {
public static void main(String[] args) {
try {
// Create sample activity data
ActivityData activityData = createSampleActivity();
// Create FIT file
FitFileCreator creator = new FitFileCreator();
creator.createActivityFile("sample_activity.fit", activityData);
System.out.println("FIT file created successfully");
} catch (Exception e) {
e.printStackTrace();
}
}
private static ActivityData createSampleActivity() {
ActivityData activity = new ActivityData();
activity.setStartTime(ZonedDateTime.now().minusHours(1));
activity.setTotalTime(3600); // 1 hour
activity.setTotalDistance(10000); // 10 km
activity.setTotalCalories(500);
activity.setSport(Sport.RUNNING);
activity.setSubSport(SubSport.GENERIC);
// Create sample laps
List<LapData> laps = new ArrayList<>();
LapData lap1 = new LapData();
lap1.setStartTime(ZonedDateTime.now().minusHours(1));
lap1.setEndTime(ZonedDateTime.now().minusMinutes(30));
lap1.setTotalTime(1800);
lap1.setDistance(5000);
lap1.setCalories(250);
lap1.setAvgHeartRate((short) 150);
lap1.setMaxHeartRate((short) 165);
lap1.setAvgSpeed(2.78); // 10 km/h
laps.add(lap1);
activity.setLaps(laps);
// Create sample records
List<RecordData> records = new ArrayList<>();
for (int i = 0; i < 10; i++) {
RecordData record = new RecordData();
record.setTimestamp(ZonedDateTime.now().minusHours(1).plusMinutes(i * 6));
record.setDistance(i * 1000.0);
record.setSpeed(2.78);
record.setHeartRate((short) (140 + i));
records.add(record);
}
activity.setRecords(records);
return activity;
}
}
3. Batch Processing
public class BatchFitProcessor {
public void processDirectory(String directoryPath) {
File dir = new File(directoryPath);
File[] fitFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".fit"));
if (fitFiles == null) return;
AdvancedFitProcessor processor = new AdvancedFitProcessor();
List<FitAnalysisResult> results = new ArrayList<>();
for (File fitFile : fitFiles) {
try {
System.out.println("Processing: " + fitFile.getName());
FitAnalysisResult result = processor.analyzeFitFile(fitFile.getAbsolutePath());
results.add(result);
// Generate report for each file
generateIndividualReport(fitFile.getName(), result);
} catch (Exception e) {
System.err.println("Failed to process " + fitFile.getName() + ": " + e.getMessage());
}
}
// Generate summary report
generateSummaryReport(results);
}
private void generateIndividualReport(String filename, FitAnalysisResult result) {
System.out.println("\n=== Report for " + filename + " ===");
result.printSummary();
}
private void generateSummaryReport(List<FitAnalysisResult> results) {
System.out.println("\n=== Batch Processing Summary ===");
System.out.println("Total files processed: " + results.size());
double totalDistance = results.stream()
.mapToDouble(FitAnalysisResult::getTotalDistance)
.sum();
double avgDistance = results.stream()
.mapToDouble(FitAnalysisResult::getTotalDistance)
.average()
.orElse(0.0);
System.out.printf("Total distance: %.2f meters%n", totalDistance);
System.out.printf("Average distance per activity: %.2f meters%n", avgDistance);
}
}
Best Practices
- Error Handling: Always validate FIT file integrity
- Memory Management: Use streaming for large FIT files
- Data Validation: Check for required fields and valid ranges
- Performance: Cache frequently accessed data
- Logging: Implement comprehensive logging for debugging
// Example of robust error handling
public class SafeFitReader {
public Optional<FitData> readFitFileSafely(String filePath) {
try {
BasicFitReader reader = new BasicFitReader();
return Optional.of(reader.readFitFile(filePath));
} catch (IOException e) {
System.err.println("I/O error reading FIT file: " + e.getMessage());
return Optional.empty();
} catch (FitRuntimeException e) {
System.err.println("FIT decoding error: " + e.getMessage());
return Optional.empty();
} catch (Exception e) {
System.err.println("Unexpected error: " + e.getMessage());
return Optional.empty();
}
}
}
Conclusion
FIT file processing in Java provides:
- Comprehensive fitness data analysis capabilities
- Efficient binary format handling with small file sizes
- Advanced metrics calculation for performance analysis
- Flexible data export and creation options
- Professional-grade error handling and validation
This implementation demonstrates how to create a complete FIT file processing system, from basic reading to advanced analysis, GPS track processing, and FIT file creation. The modular design allows for easy extension and integration into larger fitness applications.
Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)
https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.
https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.
https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.
https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.
https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)
https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.
https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.
https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.
https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.