The below is not so much as a test but rather a sample code how to deal with a FIX 4.4 message – for those who need to quick start.
The code is self-explanatory – that is if you read the FIX Protocol documentation.
package test.fix; import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.junit.Test; import quickfix.ConfigError; import quickfix.DataDictionary; import quickfix.Field; import quickfix.FieldConvertError; import quickfix.FieldMap; import quickfix.FieldNotFound; import quickfix.FieldType; import quickfix.Group; import quickfix.InvalidMessage; import quickfix.Message; import quickfix.MessageUtils; import quickfix.Message.Header; import quickfix.field.LastQty; import quickfix.field.MsgType; import quickfix.field.PossDupFlag; import quickfix.field.PossResend; import quickfix.field.SecurityID; import quickfix.field.TargetCompID; public class TestFix1 { // http://www.quickfixj.org/downloads/ private final char ONE = '\001'; @Test public void test() throws ConfigError, InvalidMessage, FieldNotFound, IOException { // 8=FIX.4.4^9=297^35=AE^49=AIM5^56=TILQ^17=905881^22=1^31=275.121547^32=18100^38=4979700^48=SMF6^55=SMF6^60=20151210-18:33:49.000^64=20151210^75=20151210^150=F^487=0^570=N^571=905881^552=1^54=2^37=905881^11=TMC-298.1561^1=GTAACORE^136=3^137=-135.75^138=USD^139=3^891=0^137=-1.81^138=USD^139=4^891=0^137=-497.75^138=USD^139=7^891=0^10=124^ // 8=FIX.4.4^9=412^35=AE^34=3611^49=TILQ^52=20151210-18:35:59^56=GCS2^6=275.121547^14=18100^17=905881^22=1^31=275.121547^32=18100^38=4979700^39=1^48=SMF6^55=SMF6^60=20151210-18:33:49.000^64=20151210^75=20151210^150=F^151=4961600^487=0^570=N^571=TMC-298.1568^552=1^54=2^37=TMC-298.1561^11=TMCBBG-TICKET-2015-12-10-905881^1=GTAACORE^136=3^137=-135.75^138=USD^139=3^891=0^137=-1.81^138=USD^139=4^891=0^137=-497.75^138=USD^139=7^891=0^10=017^ String messageStringHR="8=FIX.4.4^9=195^35=AE^49=AIM5^56=TILQ^17=740223^22=8^31=99.5^32=492200000^" + "38=489739000^48=US912828L997^55=T^60=20151030-17:42:59.000^64=20151102^75=20151030^150=F^" + "487=0^570=N^571=740223^552=1^54=1^37=740223^11=TMC-270.2^1=NT7H^10=126^"; messageStringHR="8=FIX.4.4^9=297^35=AE^49=AIM5^56=TILQ^17=905881^22=1^31=275.121547^32=18100^38=4979700^" + "48=SMF6^55=SMF6^60=20151210-18:33:49.000^64=20151210^75=20151210^150=F^487=0^570=N^571=905881^552=1^" + "54=2^37=905881^11=TMC-298.1561^1=GTAACORE^136=3^137=-135.75^138=USD^139=3^891=0^137=-1.81^138=USD^" + "139=4^891=0^137=-497.75^138=USD^139=7^891=0^10=124^"; String messageString=messageStringHR.replace('^', ONE); DataDictionary dataDict = DataDictionaryUtils.loadDataDictionary("TIL_CORE_FIX44.xml"); FixMessagePrinter fixMessagePrinter = new FixMessagePrinter(); Message message = DataDictionaryUtils.parseRawFixMsg(dataDict, messageString, false); System.out.printf("Raw String:%n%n%s%n%n", messageString); System.out.printf("FixMsgObj:%n%n%s%n", fixMessagePrinter.print(message, dataDict, 4)); System.out.println("LastQty="+message.getDecimal(LastQty.FIELD)); FieldMapReader r = new FieldMapReader(message); System.out.println("TargetCompID="+r.getDecimal(TargetCompID.FIELD)); } } // Helper classes class FixMessagePrinter { private int m_indent = 4; private static final String CR = System.getProperty("line.separator"); /** * Default constructor */ public FixMessagePrinter() { } /** * @return the indent */ public int getIndent() { return m_indent; } /** * @param indent * the indent to set */ public void setIndent(int indent) { m_indent = indent; } /** * * @param message * @return */ public String print(Message message) { return print(message, null, m_indent); } /** * * @param message * @param dataDictionary * @param indent space indentation for each level * @return */ public String print(Message message, DataDictionary dataDictionary, int indent) { StringBuffer writer = new StringBuffer(); String levelSpacing = getIndentSpacing(indent); printFields(writer, "", levelSpacing, "header", message.getHeader(), dataDictionary); printFields(writer, "", levelSpacing, "body", message, dataDictionary); printFields(writer, "", levelSpacing, "trailer", message.getTrailer(), dataDictionary); return writer.toString(); } private String getIndentSpacing(int indent) { StringBuffer sb = new StringBuffer(indent); for (int i=0; i < indent; i++) { sb.append(" "); } return sb.toString(); } private void printFields(StringBuffer writer, String currentSpacing, String levelSpacing, String section, FieldMap fieldMap, DataDictionary dataDictionary) { Iterator<Field<?>> fieldItr = fieldMap.iterator(); while (fieldItr.hasNext()) { Field<?> field = fieldItr.next(); if (isGroupCountField(dataDictionary, field)) { // skip group count field continue; } formatOneField(writer, currentSpacing, field.getTag(), field.getObject().toString(), dataDictionary); } // group // noGroup(tagID) = groupCount // // field 1 = a // field 2 = b // field 1 = d // field 2 = e Iterator<Integer> groupKeyItr = fieldMap.groupKeyIterator(); while (groupKeyItr.hasNext()) { int groupTagID = ((Integer) groupKeyItr.next()).intValue(); int groupCount = fieldMap.getGroupCount(groupTagID); writer.append(CR); formatOneField(writer, currentSpacing, groupTagID, Integer.toString(groupCount), dataDictionary); // group elements List<Group> groups = fieldMap.getGroups(groupTagID); Iterator<Group> groupItr = groups.iterator(); final String nextLevelSpacing = currentSpacing + levelSpacing; while (groupItr.hasNext()) { Group group = (Group) groupItr.next(); writer.append(CR); printFields(writer, nextLevelSpacing, levelSpacing, "group-" + groupTagID, group, dataDictionary); } } } private boolean isGroupCountField(DataDictionary dd, Field<?> field) { if (dd == null) { return false; } FieldType fieldTypeEnum = dd.getFieldTypeEnum(field.getTag()); return fieldTypeEnum != null && fieldTypeEnum == FieldType.NumInGroup; } /** * @param writer * @param field * @param dataDictionary */ private void formatOneField(StringBuffer writer, String currentSpacing, int tagID, String tagValue, DataDictionary dataDictionary) { // tagName(tagID) = "abc def" // tagName(tagID) = enumName(1) writer.append(currentSpacing); String tagName = null; String enumName = null; if (dataDictionary != null) { tagName = dataDictionary.getFieldName(tagID); enumName = dataDictionary.getValueName(tagID, tagValue); } if (tagName == null) { /// tagID = writer.append(tagID); } else { // tagName(tagID) = writer.append(tagName).append("(").append(tagID).append(")"); } writer.append(" = "); if (enumName == null) { // "value" writer.append("\"").append(tagValue).append("\""); } else { // enumName(value) writer.append(enumName).append("(").append(tagValue).append(")"); } writer.append(CR); } } /** * QuickFixJ getter wrapper to get default value instead of FieldNotFound * Exception * */ class FieldMapReader { private FieldMap m_fieldMap = null; private String m_defaultString = null; private BigDecimal m_defaultDecimal = null; private BigInteger m_defaultBigInt = null; private Integer m_defaultInteger = null; private double m_defaultDouble = 0; private int m_defaultInt = 0; private char m_defaultChar = '\u0000'; private boolean m_defaultBoolean = false; private Date m_defaultDate = null; private DateTimeFormatter m_localDateFormat = DateTimeFormat.forPattern("yyyyMMdd"); private DateTimeFormatter m_localTimeFormat = DateTimeFormat.forPattern("HH:mm:ss"); private DateTimeFormatter m_localTimeMillisFormat = DateTimeFormat.forPattern("HH:mm:ss.SSS"); private DateTimeFormatter m_localTimestampFormat = DateTimeFormat.forPattern("yyyyMMdd-HH:mm:ss"); private DateTimeFormatter m_localTimestampMillisFormat = DateTimeFormat.forPattern("yyyyMMdd-HH:mm:ss.SSS"); private Header m_header = null; private FieldMapReader m_headerReader = null; public FieldMapReader() { } public FieldMapReader(FieldMap fieldMap) { setFieldMap(fieldMap); } /** * @return the fieldMap */ public FieldMap getFieldMap() { return m_fieldMap; } /** * @param fieldMap * the fieldMap to set */ public void setFieldMap(FieldMap fieldMap) { m_fieldMap = fieldMap; if (fieldMap instanceof Message) { m_header = ((Message) fieldMap).getHeader(); m_headerReader = new FieldMapReader(m_header); } } /** * @return the headerReader */ public FieldMapReader getHeaderReader() { return m_headerReader; } /** * * @param mustExist * @return header msgType */ public String getMsgType(boolean mustExist) { return getMsgType(getHeaderReader(), mustExist); } /** * * @param headerReader * @param mustExist * @return */ public static String getMsgType(FieldMapReader headerReader, boolean mustExist) { if (headerReader == null) { throw new RuntimeException("headerReader is null"); } else { return headerReader.getString(MsgType.FIELD, mustExist); } } /** * @return the defaultBigInt */ public BigInteger getDefaultBigInt() { return m_defaultBigInt; } /** * @param defaultBigInt * the defaultBigInt to set */ public void setDefaultBigInt(BigInteger defaultBigInt) { m_defaultBigInt = defaultBigInt; } /** * @return the defaultInteger */ public Integer getDefaultInteger() { return m_defaultInteger; } /** * @param defaultInteger * the defaultInteger to set */ public void setDefaultInteger(Integer defaultInteger) { m_defaultInteger = defaultInteger; } /** * @return the defaultInt */ public int getDefaultInt() { return m_defaultInt; } /** * @param defaultInt * the defaultInt to set */ public void setDefaultInt(int defaultInt) { m_defaultInt = defaultInt; } /** * @return the defaultString */ public String getDefaultString() { return m_defaultString; } /** * @param defaultString * the defaultString to set */ public void setDefaultString(String defaultString) { m_defaultString = defaultString; } /** * @return the defaultDecimal */ public BigDecimal getDefaultDecimal() { return m_defaultDecimal; } /** * @param defaultDecimal * the defaultDecimal to set */ public void setDefaultDecimal(BigDecimal defaultDecimal) { m_defaultDecimal = defaultDecimal; } /** * @return the defaultDouble */ public double getDefaultDouble() { return m_defaultDouble; } /** * @param defaultDouble * the defaultDouble to set */ public void setDefaultDouble(double defaultDouble) { m_defaultDouble = defaultDouble; } /** * @return the defaultChar */ public char getDefaultChar() { return m_defaultChar; } /** * @param defaultChar * the defaultChar to set */ public void setDefaultChar(char defaultChar) { m_defaultChar = defaultChar; } /** * @return the defaultBoolean */ public boolean isDefaultBoolean() { return m_defaultBoolean; } /** * @param defaultBoolean * the defaultBoolean to set */ public void setDefaultBoolean(boolean defaultBoolean) { m_defaultBoolean = defaultBoolean; } /** * @return * @see quickfix.FieldMap#isEmpty() */ public boolean isEmpty() { return m_fieldMap.isEmpty(); } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getString(int) */ public String getString(int field) { return getString(field, m_defaultString); } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getString(int) */ public String getString(int field, String defaultValue) { try { return m_fieldMap.getString(field); } catch (FieldNotFound e) { return defaultValue; } } /** * @param field * @param mustExist * true – field must exist, false – optional field * @return */ public String getString(int field, boolean mustExist) { try { return m_fieldMap.getString(field); } catch (FieldNotFound e) { if (mustExist) { throw new RuntimeException(e); } else { return m_defaultString; } } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getBoolean(int) */ public Boolean getBoolean(int field) { return getBoolean(field, m_defaultBoolean); } /** * @param field * @param defaultValue * @return * @see quickfix.FieldMap#getBoolean(int) */ public Boolean getBoolean(int field, Boolean defaultValue) { try { return m_fieldMap.getBoolean(field); } catch (FieldNotFound e) { return defaultValue; } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getChar(int) */ public char getChar(int field) { try { return m_fieldMap.getChar(field); } catch (FieldNotFound e) { return m_defaultChar; } } /** * @param field * @param mustExist * true – field must exist, false – optional field * @return */ public char getChar(int field, boolean mustExist) { try { return m_fieldMap.getChar(field); } catch (FieldNotFound e) { if (mustExist) { throw new RuntimeException(e); } else { return m_defaultChar; } } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getDouble(int) */ public double getDouble(int field) { try { return m_fieldMap.getDouble(field); } catch (FieldNotFound e) { return m_defaultDouble; } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getInt(int) */ public int getInt(int field) { try { return m_fieldMap.getInt(field); } catch (FieldNotFound e) { return m_defaultInt; } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getDecimal(int) */ public BigDecimal getDecimal(int field) { return getDecimal(field, false); } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getDecimal(int) */ public BigDecimal getDecimal(int field, boolean mustExist) { return getDecimal(field, mustExist, m_defaultDecimal); } /** * * @param field * @param mustExist * @param defaultValue * @return */ public BigDecimal getDecimal(int field, boolean mustExist, BigDecimal defaultValue) { try { return m_fieldMap.getDecimal(field); } catch (FieldNotFound e) { if (mustExist) { throw new RuntimeException(e); } else { return defaultValue; } } } /** * @param field * @return * @throws FieldNotFound */ public Integer getInteger(int field) { return getInteger(field, false); } /** * @param field * @param mustExist * true – field must exist, false – optional field * @return */ public Integer getInteger(int field, boolean mustExist) { return getInteger(field, mustExist, m_defaultInteger); } /** * @param field * @param mustExist * true – field must exist, false – optional field * @param defaultValue * @return */ public Integer getInteger(int field, boolean mustExist, Integer defaultValue) { String value = null; try { value = m_fieldMap.getString(field); return new Integer(value); } catch (FieldNotFound e) { if (mustExist) { throw new RuntimeException(e); } else { return defaultValue; } } } /** * @param field * @param mustExist * true – field must exist, false – optional field * @param defaultValue * @return */ public Long getLong(int field, boolean mustExist, Long defaultValue) { String value = null; try { value = m_fieldMap.getString(field); return new Long(value); } catch (FieldNotFound e) { if (mustExist) { throw new RuntimeException(e); } else { return defaultValue; } } } /** * @param field * @return * @throws FieldNotFound */ public BigInteger getBigInt(int field) { return getBigInt(field, false); } /** * @param field * @param mustExist * true – field must exist, false – optional field * @return */ public BigInteger getBigInt(int field, boolean mustExist) { String value = null; try { value = m_fieldMap.getString(field); return convertToBigInteger(value); } catch (FieldNotFound e) { if (mustExist) { throw new RuntimeException(e); } else { return m_defaultBigInt; } } catch (FieldConvertError e) { throw newBadFieldException(field, value, e); } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getUtcTimeStamp(int) */ public Date getUtcTimeStamp(int field) { try { return m_fieldMap.getUtcTimeStamp(field); } catch (FieldNotFound e) { return m_defaultDate; } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getUtcTimeOnly(int) */ public Date getUtcTimeOnly(int field) { try { return m_fieldMap.getUtcTimeOnly(field); } catch (FieldNotFound e) { return m_defaultDate; } } /** * @param field * @return * @throws FieldNotFound * @see quickfix.FieldMap#getUtcDateOnly(int) */ public Date getUtcDateOnly(int field) { try { return m_fieldMap.getUtcDateOnly(field); } catch (FieldNotFound e) { return m_defaultDate; } } /** * @param field * @return * @see quickfix.FieldMap#isSetField(int) */ public boolean isSetField(int field) { return m_fieldMap.isSetField(field); } /** * @param field * @return * @see quickfix.FieldMap#getGroups(int) */ // public List<Group> getGroups(int field) // { // return m_fieldMap.getGroups(field); // } /** * * @param field * @return group as {@link FieldMapReader} */ public List<FieldMapReader> getGroups(int field) { List<FieldMapReader> list = new ArrayList<FieldMapReader>(); List<Group> l = m_fieldMap.getGroups(field); for (Group group : l) { list.add(new FieldMapReader(group)); } return list; } /** * * @param field * @return */ public Date getLocalDateOnly(int field) { // always pick the second formatter return getDateField(field, 0, m_localDateFormat, m_localDateFormat); } /** * * @param field * @return */ public Date getLocalTimeOnly(int field) { // HH:mm:ss (8) vs HH:mm:ss.SSS return getDateField(field, 8, m_localTimeFormat, m_localTimeMillisFormat); } /** * * @param field * @return */ public Date getLocalTimeStamp(int field) { // yyyyMMdd-HH:mm:ss (17) vs yyyyMMdd-HH:mm:ss.SSS return getDateField(field, 17, m_localTimestampFormat, m_localTimestampMillisFormat); } /** * * @param field * @param valueLen1 * value length for choosing formatter1 * @param formatter1 * @param formatter2 * for value length != valueLen1 * @return date based on supplied formatter */ private Date getDateField(int field, int strLen1, DateTimeFormatter formatter1, DateTimeFormatter formatter2) { String value = null; try { value = m_fieldMap.getString(field); DateTimeFormatter formatter = value.length() == strLen1 ? formatter1 : formatter2; return formatter.parseDateTime(value).toDate(); } catch (FieldNotFound e) { return m_defaultDate; } catch (Exception e) { throw newBadFieldException(field, value, e); } } private RuntimeException newBadFieldException(int field, String value, Exception e) { String message = "invalid field value = [" + value == null ? "" : value + "] for tag = [" + field + "]"; return new RuntimeException(message, e); } /** * Convert a String to a BigInteger. * @param value the String to convert * @return the converted integer * @throws FieldConvertError raised if the String does not represent a * valid integer. * @see java.lang.Integer#parseInt(String) */ private static BigInteger convertToBigInteger(String value) throws FieldConvertError { try { for (int i = 0; i < value.length(); i++) { if (!Character.isDigit(value.charAt(i)) && !(i == 0 && value.charAt(i) == '-')) { throw new FieldConvertError("invalid integral value: " + value); } } return new BigInteger(value); } catch (NumberFormatException e) { throw new FieldConvertError("invalid integral value: " + value + ": " + e); } } /** * * @return is either PossDupFlag (tag 43) is set or PossResend (tag 97) is * set */ public boolean isPossDupOrPossResend() { return getHeaderReader().getBoolean(PossDupFlag.FIELD, false) || getHeaderReader().getBoolean(PossResend.FIELD, false); } /** * return tagID if possDup flag is set or possResend flag is set in the * header * * @return null if none of the flag is set to true */ public Integer getPossDupOrPossResendTagIfSet() { return getPossDupOrPossResendTagIfSet(getHeaderReader()); } /** * return tagID if possDup flag is set or possResend flag is set in the * headerReader * * @param headerReader * @return */ public static Integer getPossDupOrPossResendTagIfSet(FieldMapReader headerReader) { if (headerReader == null) { return null; } else if (headerReader.getBoolean(PossDupFlag.FIELD, false)) { return PossDupFlag.FIELD; } else if (headerReader.getBoolean(PossResend.FIELD, false)) { return PossResend.FIELD; } else { return null; } } } final class DataDictionaryUtils { private DataDictionaryUtils() { } /** * load the data dictionary from the resource on the classpath * @param resourceName * @return * @throws ConfigError * @throws IOException */ public static DataDictionary loadDataDictionary(String resourceName) throws ConfigError, IOException { InputStream ddStream = getResourceInputStream(resourceName); if (ddStream == null) { throw new RuntimeException("Unable to load data dictionary from resource = [" + resourceName + "]"); } DataDictionary dataDictionary = loadDataDictionary(ddStream); return dataDictionary; } private static InputStream getResourceInputStream(String resourceName) throws IOException { Path path = FileSystems.getDefault().getPath(".", resourceName); Files.newInputStream(path, StandardOpenOption.READ); return null; } /** * Load the data dictionary from the input stream * * @param inputStream * @return * @throws ConfigError */ public static DataDictionary loadDataDictionary(InputStream inputStream) throws ConfigError { DataDictionary dataDictionary = new DataDictionary(inputStream); // remember to set setCheckUnorderedGroupFields to false or else // message generated from quickfix (which is out of order inside group) // will not // be parsed back by quick fix dataDictionary.setCheckUnorderedGroupFields(false); return dataDictionary; } /** * parse raw FIX message and return the QuickFix Message Object Once you get * the quickFix message object, you can use the following codes to print the * FIX message: * * <pre> * <code> * m_fixMessagePrinter.print(msgObj, m_dataDictionary, indentation); * </code> * </pre> * * @param dataDictionary * see {@link #loadDataDictionary(String)} or * {@link #loadDataDictionary(InputStream)} * @param rawMessage * raw FIX message string in SOH format * @param doValidation * true – validate FIX message according to dataDictionary; false * – no validation * @return quickFix message object * @throws InvalidMessage */ public static Message parseRawFixMsg(DataDictionary dataDictionary, String rawMessage, boolean doValidation) throws InvalidMessage { if (dataDictionary == null || rawMessage == null) { return null; } Message msgObj = new Message(); msgObj.fromString(rawMessage, dataDictionary, doValidation); return msgObj; } }