diff --git a/crates/io/src/dxf_read/entities.rs b/crates/io/src/dxf_read/entities.rs
--- a/crates/io/src/dxf_read/entities.rs
+++ b/crates/io/src/dxf_read/entities.rs
@@ -1,6 +1,6 @@
 use super::common::{T, common, ocs};
 use super::hatch::hatch;
-use super::text::{attdef, attrib_modes, attrib_props, mtext_from, multiline_attrib, split_embedded, text_from};
+use super::text::{attdef, attrib_modes, attrib_props, attrib_text_groups, mtext_from, multiline_attrib, split_embedded, text_from};
 use super::*;
 
 /// Parse one entity record. POLYLINE/INSERT followers are handled by the caller.
@@ -526,8 +526,8 @@ pub(super) fn insert_attribs(recs: &[(String, Vec<Tag>)], i: &mut usize, ins_lay
             // A multiline attribute has its text as embedded MTEXT after its own groups.
             let (own, embedded) = split_embedded(t2);
             let multiline = multiline_attrib(embedded, &common(&tt));
+            let mut text = text_from(&T(&attrib_text_groups(own)));
             let own = T(own);
-            let mut text = text_from(&own);
             if let Some(m) = &multiline {
                 text.value.clone_from(&m.read_as);
             }
diff --git a/crates/io/src/dxf_read/text.rs b/crates/io/src/dxf_read/text.rs
--- a/crates/io/src/dxf_read/text.rs
+++ b/crates/io/src/dxf_read/text.rs
@@ -33,6 +33,18 @@ pub(super) fn split_embedded(tags: &[Tag]) -> (&[Tag], Option<&[Tag]>) {
     }
 }
 
+/// The groups of an ATTRIB's or ATTDEF's own groups that describe its text: all of them up to
+/// its `AcDbAttribute`/`AcDbAttributeDefinition` subclass, and from there on all but the field
+/// length (73) and the multiline header (71, 72 and the point 11): there 72 isn't the
+/// justification nor 11 the alignment point, and a left-justified attribute writes no 72 of its
+/// own, so the multiline one was read as centred. The vertical justification is that
+/// subclass's 74.
+pub(super) fn attrib_text_groups(own: &[Tag]) -> Vec<Tag> {
+    let at = own.iter().position(|t| t.code == 100 && t.str().trim().starts_with("AcDbAttribute")).unwrap_or(own.len());
+    let (text, attrib) = own.split_at(at);
+    text.iter().chain(attrib.iter().filter(|t| !matches!(t.code, 71 | 72 | 73 | 11 | 21 | 31))).cloned().collect()
+}
+
 /// A multiline attribute's text, from the MTEXT groups embedded in it.
 pub(super) fn multiline_attrib(embedded: Option<&[Tag]>, c: &Common) -> Option<MultilineAttrib> {
     let mtext = mtext_from(embedded.filter(|e| !e.is_empty())?, c);
@@ -157,8 +169,8 @@ pub(super) fn attdef(tags: &[Tag], c: &Common) -> Attrib {
     // A multiline definition has its text as embedded MTEXT after its own groups.
     let (own, embedded) = split_embedded(tags);
     let multiline = multiline_attrib(embedded, c);
+    let mut text = text_from(&T(&attrib_text_groups(own)));
     let own = T(own);
-    let mut text = text_from(&own);
     if let Some(m) = &multiline {
         text.value.clone_from(&m.read_as);
     }
diff --git a/crates/io/tests/it/multiline_attributes.rs b/crates/io/tests/it/multiline_attributes.rs
--- a/crates/io/tests/it/multiline_attributes.rs
+++ b/crates/io/tests/it/multiline_attributes.rs
@@ -5,7 +5,7 @@
 //! The fixture is synthetic, laid out as the DXF Reference describes these attributes: block TAG
 //! with a multiline definition LABEL, and a reference whose LABEL attribute has its own text.
 
-use cadcraft_doc::{Drawing, EntityKind};
+use cadcraft_doc::{Drawing, EntityKind, HAlign, VAlign};
 use cadcraft_io::{SaveOptions, Version, read_dxf};
 
 /// DXF text from lines of `code value` (the value may be empty).
@@ -309,6 +309,42 @@ fn an_edited_value_replaces_the_multiline_text() {
     assert_eq!(reference(&again).attribs[0].text.value, "SOUTH {A}\nWELL 8");
 }
 
+/// The fixture with the reference's attribute left-justified on its baseline: its text groups
+/// carry no 72, 11 or 74, only its multiline header (71 2, 72 1, 11) does.
+fn left_justified() -> String {
+    dxf(&FIXTURE.replacen("72 1\n11 110\n21 53\n31 0\n100 AcDbAttribute", "100 AcDbAttribute", 1).replacen("70 0\n74 1\n", "70 0\n", 1))
+}
+
+/// An attribute's justification comes from its text groups (#2255). In its attribute subclass
+/// 71, 72 and 11 are the multiline header and 73 the field length, not the justification,
+/// alignment point and vertical justification (that is 74): a left-justified multiline attribute
+/// was read as centred, and its next save moved its single-line text.
+#[test]
+fn justification_is_read_from_the_text_groups() {
+    let d = read_dxf(left_justified().as_bytes()).unwrap();
+    let left = |d: &Drawing| {
+        let t = reference(d).attribs[0].text.clone();
+        assert_eq!((t.halign, t.valign, t.align_pt), (HAlign::Left, VAlign::Baseline, None));
+        assert_eq!((t.insert.x, t.insert.y), (102.0, 52.0));
+    };
+    left(&d);
+    // The definition's field length (73 0) isn't its vertical justification (74 1).
+    let ad = definition(&d);
+    assert_eq!((ad.text.halign, ad.text.valign), (HAlign::Center, VAlign::Bottom));
+    // Saved twice, it stays where it was, and so does the definition.
+    let once = read_dxf(save(&d).as_bytes()).unwrap();
+    left(&once);
+    let twice = read_dxf(save(&once).as_bytes()).unwrap();
+    left(&twice);
+    assert_eq!(definition(&twice), definition(&d));
+    // An older version written after a 2018 save places the text as one written directly.
+    let r2000 = |d: &Drawing| {
+        let out = cadcraft_io::write_with(d, "x.dxf", &SaveOptions { version: Some(Version::R2000), binary: false }).unwrap();
+        reference(&read_dxf(&out).unwrap()).attribs[0].text.clone()
+    };
+    assert_eq!(r2000(&twice), r2000(&d));
+}
+
 #[test]
 fn files_before_2007_get_single_line_attributes() {
     let d = read_dxf(dxf(FIXTURE).as_bytes()).unwrap();
diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md
--- a/docs/file-format-parity.md
+++ b/docs/file-format-parity.md
@@ -1,6 +1,6 @@
 # File-format parity
 
-> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (DIMASSOC objects for angular, diametric and radial dimensions) · **Target:** Autodesk AutoCAD 2027
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-27 · **Change:** minor (a multiline attribute's justification is read from its text groups) · **Target:** Autodesk AutoCAD 2027
 
 Every format AutoCAD 2027 reads or writes, with CADCraft's support, fidelity and tests. Work
 items are in [gaps.md](gaps.md#file-formats); the weighted score feeds
@@ -75,7 +75,7 @@ on read with a note, so such files no longer exceed the DXF limit (#1482). **DWG
 |---|---|---|---|
 | LINE, POINT, CIRCLE, ARC, ELLIPSE, LWPOLYLINE (widths, bulges, elevation), SPLINE, RAY, XLINE | yes | yes | round-trip tests |
 | POLYLINE 2D/3D | yes | yes (as LWPOLYLINE / 3D POLYLINE) | polyface and polygon meshes flattened: lossy |
-| TEXT, MTEXT, ATTDEF, INSERT + ATTRIB | yes; multiline attributes (embedded MTEXT) read and drawn | yes; multiline attributes written as embedded MTEXT (DXF 2007 and later; older versions: one line) | ATTDEF prompt kept (#137) |
+| TEXT, MTEXT, ATTDEF, INSERT + ATTRIB | yes; multiline attributes (embedded MTEXT) read and drawn | yes; multiline attributes written as embedded MTEXT (DXF 2007 and later; older versions: one line) | ATTDEF prompt kept (#137); an attribute's justification comes from its text groups, not its multiline header or field length (#2255) |
 | DIMENSION (all 7 kinds) | yes | yes, with anonymous `*D` blocks | a reopened dimension's `*D` block is regenerated under its name, unreferenced ones dropped (#495); DIMASSOC objects written for every associated kind — linear/aligned, angular, radial/diametric, ordinate and arc length — and read back without CADCraft's xdata (#1651) |
 | DIMENSION (all 7 kinds) | yes | yes, with anonymous `*D` blocks | a reopened dimension's `*D` block is regenerated under its name, unreferenced ones dropped (#495); DIMASSOC objects written; angular block text honors DIMDSEP, including per-dimension overrides (#1646) |
 | DIMENSION (all 7 kinds) | yes | yes, with anonymous `*D` blocks; their text keeps the diameter sign as `%%c`, so SHX styles plot it (#1645) | a reopened dimension's `*D` block is regenerated under its name, unreferenced ones dropped (#495); DIMASSOC objects written |
@@ -140,6 +140,7 @@ our output in AutoCAD black-box.
 
 | Date | Change | Summary |
 |---|---|---|
+| 2026-10-27 | minor | DXF read: an ATTRIB's or ATTDEF's justification and alignment point come from its text groups; the multiline header (71, 72, 11) and field length (73) of its attribute subclass were read as them, so a left-justified multiline attribute became centred and its next save moved its single-line text (#2255) |
 | 2026-10-22 | minor | DXF/DWG save: angular, diametric and radial dimensions get standard DIMASSOC objects (one centre reference to the circle for radial/diametric, the definition points for angular), so AutoCAD follows their geometry; the standard objects of those kinds are read back too, without CADCraft's xdata (#1651) |
 | 2026-10-19 | minor | DXF/DWG save: angular dimension block text honors DIMDSEP, including per-dimension overrides (#1646) |
 | 2026-10-19 | minor | A file's own arrowhead blocks with standard names (AutoCAD's `_Dot`, `_DotSmall`), named only by a dimension style, are kept and saved as they were instead of being replaced by CADCraft's solids; PURGE keeps arrowhead blocks dimension styles name |
